Initial commit
This commit is contained in:
		
						commit
						8c1ffafb85
					
				| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					ASGI config for CourseField project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It exposes the ASGI callable as a module-level variable named ``application``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information on this file, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.core.asgi import get_asgi_application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CourseField.settings')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					application = get_asgi_application()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,135 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					Django settings for CourseField project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Generated by 'django-admin startproject' using Django 5.0.3.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information on this file, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/5.0/topics/settings/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the full list of settings and their values, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/5.0/ref/settings/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					import apps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build paths inside the project like this: BASE_DIR / 'subdir'.
 | 
				
			||||||
 | 
					BASE_DIR = Path(__file__).resolve().parent.parent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Quick-start development settings - unsuitable for production
 | 
				
			||||||
 | 
					# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SECURITY WARNING: keep the secret key used in production secret!
 | 
				
			||||||
 | 
					SECRET_KEY = 'django-insecure-zb6faz4q4#=5+9k-ghn+!9aiqv2_&)=n$h5$hmo&s+h8ot23=6'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SECURITY WARNING: don't run with debug turned on in production!
 | 
				
			||||||
 | 
					DEBUG = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALLOWED_HOSTS = ['*']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application definition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					INSTALLED_APPS = [
 | 
				
			||||||
 | 
					    'django.contrib.admin',
 | 
				
			||||||
 | 
					    'django.contrib.auth',
 | 
				
			||||||
 | 
					    'django.contrib.contenttypes',
 | 
				
			||||||
 | 
					    'django.contrib.sessions',
 | 
				
			||||||
 | 
					    'django.contrib.messages',
 | 
				
			||||||
 | 
					    'django.contrib.staticfiles',
 | 
				
			||||||
 | 
					    'apps.users',
 | 
				
			||||||
 | 
					    'apps.courses'
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MIDDLEWARE = [
 | 
				
			||||||
 | 
					    'django.middleware.security.SecurityMiddleware',
 | 
				
			||||||
 | 
					    'django.contrib.sessions.middleware.SessionMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.common.CommonMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.csrf.CsrfViewMiddleware',
 | 
				
			||||||
 | 
					    'django.contrib.auth.middleware.AuthenticationMiddleware',
 | 
				
			||||||
 | 
					    'django.contrib.messages.middleware.MessageMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ROOT_URLCONF = 'CourseField.urls'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEMPLATES = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'BACKEND': 'django.template.backends.django.DjangoTemplates',
 | 
				
			||||||
 | 
					        'DIRS': [BASE_DIR / 'templates']
 | 
				
			||||||
 | 
					        ,
 | 
				
			||||||
 | 
					        'APP_DIRS': True,
 | 
				
			||||||
 | 
					        'OPTIONS': {
 | 
				
			||||||
 | 
					            'context_processors': [
 | 
				
			||||||
 | 
					                'django.template.context_processors.debug',
 | 
				
			||||||
 | 
					                'django.template.context_processors.request',
 | 
				
			||||||
 | 
					                'django.contrib.auth.context_processors.auth',
 | 
				
			||||||
 | 
					                'django.contrib.messages.context_processors.messages',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WSGI_APPLICATION = 'CourseField.wsgi.application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Database
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DATABASES = {
 | 
				
			||||||
 | 
					    'default': {
 | 
				
			||||||
 | 
					        'ENGINE': 'django.db.backends.sqlite3',
 | 
				
			||||||
 | 
					        'NAME': BASE_DIR / 'db.sqlite3',
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Password validation
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTH_PASSWORD_VALIDATORS = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Internationalization
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/5.0/topics/i18n/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LANGUAGE_CODE = 'en-us'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TIME_ZONE = 'UTC'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USE_I18N = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USE_TZ = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Static files (CSS, JavaScript, Images)
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/5.0/howto/static-files/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					STATIC_URL = 'static/'
 | 
				
			||||||
 | 
					STATICFILES_DIRS = [
 | 
				
			||||||
 | 
					    BASE_DIR / 'static',
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MEDIA_URL = '/'
 | 
				
			||||||
 | 
					MEDIA_ROOT = BASE_DIR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default primary key field type
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTH_USER_MODEL = 'users.User'
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					URL configuration for CourseField project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `urlpatterns` list routes URLs to views. For more information please see:
 | 
				
			||||||
 | 
					    https://docs.djangoproject.com/en/5.0/topics/http/urls/
 | 
				
			||||||
 | 
					Examples:
 | 
				
			||||||
 | 
					Function views
 | 
				
			||||||
 | 
					    1. Add an import:  from my_app import views
 | 
				
			||||||
 | 
					    2. Add a URL to urlpatterns:  path('', views.home, name='home')
 | 
				
			||||||
 | 
					Class-based views
 | 
				
			||||||
 | 
					    1. Add an import:  from other_app.views import Home
 | 
				
			||||||
 | 
					    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
 | 
				
			||||||
 | 
					Including another URLconf
 | 
				
			||||||
 | 
					    1. Import the include() function: from django.urls import include, path
 | 
				
			||||||
 | 
					    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.urls import path, include
 | 
				
			||||||
 | 
					import apps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('admin/', admin.site.urls),
 | 
				
			||||||
 | 
					    path('', include('apps.courses.urls'), name='courses'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					WSGI config for CourseField project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It exposes the WSGI callable as a module-level variable named ``application``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information on this file, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.core.wsgi import get_wsgi_application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CourseField.settings')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					application = get_wsgi_application()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from . import models
 | 
				
			||||||
 | 
					# Register your models here.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					admin.site.register(models.Course)
 | 
				
			||||||
 | 
					admin.site.register(models.Lesson)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CoursesConfig(AppConfig):
 | 
				
			||||||
 | 
					    default_auto_field = 'django.db.models.BigAutoField'
 | 
				
			||||||
 | 
					    name = 'apps.courses'
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					import glob, json
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import ffmpy3
 | 
				
			||||||
 | 
					from .models import Course, Lesson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_duration_from_ffmpeg(url):
 | 
				
			||||||
 | 
					    tup_resp = ffmpy3.FFprobe(
 | 
				
			||||||
 | 
					        inputs={url: None},
 | 
				
			||||||
 | 
					        global_options=[
 | 
				
			||||||
 | 
					            '-v', 'quiet',
 | 
				
			||||||
 | 
					            '-print_format', 'json',
 | 
				
			||||||
 | 
					            '-show_format', '-show_streams'
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    ).run(stdout=subprocess.PIPE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    meta = json.loads(tup_resp[0].decode('utf-8'))
 | 
				
			||||||
 | 
					    return int(float(meta['format']['duration']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_lesson_cover(media_path):
 | 
				
			||||||
 | 
					    # create the first frame of the video as the cover using ffmpy3
 | 
				
			||||||
 | 
					    cover_path = Path(media_path).with_suffix('.png')
 | 
				
			||||||
 | 
					    print(cover_path)
 | 
				
			||||||
 | 
					    if cover_path.exists():
 | 
				
			||||||
 | 
					        return str(cover_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ffmpy3.FFmpeg(
 | 
				
			||||||
 | 
					        inputs={media_path: None},
 | 
				
			||||||
 | 
					        outputs={cover_path: ['-ss', '00:00:00.000', '-vframes', '1']}
 | 
				
			||||||
 | 
					    ).run()
 | 
				
			||||||
 | 
					    return str(cover_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_lesson(course):
 | 
				
			||||||
 | 
					    file_types = ('*.mp4', '*.avi', '*.mkv', '*.flv')
 | 
				
			||||||
 | 
					    media_files = []
 | 
				
			||||||
 | 
					    for ext in file_types:
 | 
				
			||||||
 | 
					        media_files.extend(glob.glob(course.course_path + '/**/' + ext, recursive=True))
 | 
				
			||||||
 | 
					    for i in range(len(media_files)):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check if the lesson is already in the database
 | 
				
			||||||
 | 
					        if Lesson.objects.filter(course=course, media=media_files[i]).exists():
 | 
				
			||||||
 | 
					            print('Lesson already exists in the database')
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        lesson_title = Path(media_files[i]).name.strip(Path(media_files[i]).suffix)
 | 
				
			||||||
 | 
					        total_time = get_duration_from_ffmpeg(media_files[i])
 | 
				
			||||||
 | 
					        cover = create_lesson_cover(media_files[i])
 | 
				
			||||||
 | 
					        Lesson.objects.create(title=lesson_title, description=lesson_title, course=course, index_num=i + 1, cover=cover,
 | 
				
			||||||
 | 
					                              total_time=total_time, media=media_files[i])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_course_media():
 | 
				
			||||||
 | 
					    content = glob.glob('media/content/*', recursive=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    existing_courses_path = Course.objects.values_list('course_path', flat=True)
 | 
				
			||||||
 | 
					    if existing_courses_path:
 | 
				
			||||||
 | 
					        existing_courses_path = list(existing_courses_path)
 | 
				
			||||||
 | 
					    # compare the existing courses with the content, if the course is not in the database, add it
 | 
				
			||||||
 | 
					    for course_path in content:
 | 
				
			||||||
 | 
					        if course_path not in existing_courses_path:
 | 
				
			||||||
 | 
					            course_title = Path(course_path).name
 | 
				
			||||||
 | 
					            Course.objects.create(title=course_title, description=course_title, instructor=course_title,
 | 
				
			||||||
 | 
					                                  course_path=course_path)
 | 
				
			||||||
 | 
					            add_lesson(Course.objects.get(course_path=course_path))
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					# Generated by Django 5.0.3 on 2024-03-11 14:39
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import django.db.models.deletion
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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')),
 | 
				
			||||||
 | 
					                ('title', models.CharField(max_length=100)),
 | 
				
			||||||
 | 
					                ('description', models.TextField()),
 | 
				
			||||||
 | 
					                ('instructor', models.CharField(max_length=100)),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='Category',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('name', models.CharField(max_length=100)),
 | 
				
			||||||
 | 
					                ('courses', models.ManyToManyField(to='courses.course')),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='Lesson',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('title', models.CharField(max_length=100)),
 | 
				
			||||||
 | 
					                ('description', models.TextField()),
 | 
				
			||||||
 | 
					                ('media', models.FileField(blank=True, null=True, upload_to='media/')),
 | 
				
			||||||
 | 
					                ('total_time', models.IntegerField()),
 | 
				
			||||||
 | 
					                ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.course')),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					# Generated by Django 5.0.3 on 2024-03-18 14:49
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('courses', '0001_initial'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='course',
 | 
				
			||||||
 | 
					            name='cover',
 | 
				
			||||||
 | 
					            field=models.ImageField(blank=True, null=True, upload_to='media/covers/'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='lesson',
 | 
				
			||||||
 | 
					            name='index_num',
 | 
				
			||||||
 | 
					            field=models.IntegerField(default=1),
 | 
				
			||||||
 | 
					            preserve_default=False,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Generated by Django 5.0.3 on 2024-03-19 04:38
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('courses', '0002_course_cover_lesson_index_num'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='course',
 | 
				
			||||||
 | 
					            name='course_path',
 | 
				
			||||||
 | 
					            field=models.CharField(blank=True, max_length=100, null=True),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# Generated by Django 5.0.3 on 2024-03-19 09:55
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('courses', '0003_course_course_path'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.AddField(
 | 
				
			||||||
 | 
					            model_name='lesson',
 | 
				
			||||||
 | 
					            name='cover',
 | 
				
			||||||
 | 
					            field=models.ImageField(default='https://dummyimage.com/400x225', upload_to='media/covers/'),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.urls import reverse
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your models here.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Lesson(models.Model):
 | 
				
			||||||
 | 
					    title = models.CharField(max_length=100)
 | 
				
			||||||
 | 
					    description = models.TextField()
 | 
				
			||||||
 | 
					    course = models.ForeignKey('Course', on_delete=models.CASCADE)
 | 
				
			||||||
 | 
					    index_num = models.IntegerField()
 | 
				
			||||||
 | 
					    media = models.FileField(upload_to='media/', blank=True, null=True)
 | 
				
			||||||
 | 
					    cover = models.ImageField(upload_to='media/covers/', blank=False, default='https://dummyimage.com/400x225')
 | 
				
			||||||
 | 
					    total_time = models.IntegerField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_absolute_url(self):
 | 
				
			||||||
 | 
					        return reverse('courses:lesson-detail', args=[str(self.course.id), str(self.id)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_media_url(self):
 | 
				
			||||||
 | 
					        return self.media.url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Course(models.Model):
 | 
				
			||||||
 | 
					    title = models.CharField(max_length=100)
 | 
				
			||||||
 | 
					    description = models.TextField()
 | 
				
			||||||
 | 
					    instructor = models.CharField(max_length=100)
 | 
				
			||||||
 | 
					    cover = models.ImageField(upload_to='media/covers/', blank=True, null=True)
 | 
				
			||||||
 | 
					    course_path = models.CharField(max_length=100, blank=True, null=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_absolute_url(self):
 | 
				
			||||||
 | 
					        return reverse('courses:course-detail', args=[str(self.id)])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Category(models.Model):
 | 
				
			||||||
 | 
					    name = models.CharField(max_length=100)
 | 
				
			||||||
 | 
					    courses = models.ManyToManyField(Course)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.name
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your tests here.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					from django.conf.urls.static import static
 | 
				
			||||||
 | 
					from django.urls import path, include
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from CourseField import settings
 | 
				
			||||||
 | 
					from . import views
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app_name = 'courses'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('', views.IndexView.as_view(), name='index'),
 | 
				
			||||||
 | 
					    path('course/<int:pk>/', views.CourseDetailView.as_view(), name='course-detail'),
 | 
				
			||||||
 | 
					    path('course/<int:pk>/lesson/<int:lesson_pk>/', views.LessonDetailView.as_view(), name='lesson-detail'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,28 @@
 | 
				
			||||||
 | 
					from django.shortcuts import render
 | 
				
			||||||
 | 
					from django.views import View
 | 
				
			||||||
 | 
					from apps.courses.models import Course, Lesson
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your views here.
 | 
				
			||||||
 | 
					class IndexView(View):
 | 
				
			||||||
 | 
					    def get(self, request):
 | 
				
			||||||
 | 
					        course_list = Course.objects.all()
 | 
				
			||||||
 | 
					        context = {'course_list': course_list}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        from .auto_importer import get_course_media
 | 
				
			||||||
 | 
					        get_course_media()
 | 
				
			||||||
 | 
					        return render(request, 'courses/index.html', context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CourseDetailView(View):
 | 
				
			||||||
 | 
					    def get(self, request, pk):
 | 
				
			||||||
 | 
					        course = Course.objects.get(pk=pk)
 | 
				
			||||||
 | 
					        context = {'lesson_list': course.lesson_set.all(), 'course': course}
 | 
				
			||||||
 | 
					        return render(request, 'courses/course_detail.html', context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LessonDetailView(View):
 | 
				
			||||||
 | 
					    def get(self, request, pk, lesson_pk):
 | 
				
			||||||
 | 
					        lesson = Lesson.objects.get(pk=lesson_pk)
 | 
				
			||||||
 | 
					        context = {'lesson': lesson}
 | 
				
			||||||
 | 
					        return render(request, 'courses/lesson_detail.html', context)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Register your models here.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UsersConfig(AppConfig):
 | 
				
			||||||
 | 
					    default_auto_field = 'django.db.models.BigAutoField'
 | 
				
			||||||
 | 
					    name = 'apps.users'
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					# Generated by Django 5.0.3 on 2024-03-11 14:09
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import django.contrib.auth.models
 | 
				
			||||||
 | 
					import django.contrib.auth.validators
 | 
				
			||||||
 | 
					import django.utils.timezone
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('auth', '0012_alter_user_first_name_max_length'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='User',
 | 
				
			||||||
 | 
					            fields=[
 | 
				
			||||||
 | 
					                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
				
			||||||
 | 
					                ('password', models.CharField(max_length=128, verbose_name='password')),
 | 
				
			||||||
 | 
					                ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
 | 
				
			||||||
 | 
					                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
 | 
				
			||||||
 | 
					                ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
 | 
				
			||||||
 | 
					                ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
 | 
				
			||||||
 | 
					                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
 | 
				
			||||||
 | 
					                ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
 | 
				
			||||||
 | 
					                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
 | 
				
			||||||
 | 
					                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
 | 
				
			||||||
 | 
					                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
 | 
				
			||||||
 | 
					                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
 | 
				
			||||||
 | 
					                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            options={
 | 
				
			||||||
 | 
					                'verbose_name': 'user',
 | 
				
			||||||
 | 
					                'verbose_name_plural': 'users',
 | 
				
			||||||
 | 
					                'abstract': False,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            managers=[
 | 
				
			||||||
 | 
					                ('objects', django.contrib.auth.models.UserManager()),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.contrib.auth.models import AbstractUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your models here.
 | 
				
			||||||
 | 
					class User(AbstractUser):
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your tests here.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					from django.shortcuts import render
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your views here.
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					"""Django's command-line utility for administrative tasks."""
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    """Run administrative tasks."""
 | 
				
			||||||
 | 
					    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'CourseField.settings')
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from django.core.management import execute_from_command_line
 | 
				
			||||||
 | 
					    except ImportError as exc:
 | 
				
			||||||
 | 
					        raise ImportError(
 | 
				
			||||||
 | 
					            "Couldn't import Django. Are you sure it's installed and "
 | 
				
			||||||
 | 
					            "available on your PYTHONPATH environment variable? Did you "
 | 
				
			||||||
 | 
					            "forget to activate a virtual environment?"
 | 
				
			||||||
 | 
					        ) from exc
 | 
				
			||||||
 | 
					    execute_from_command_line(sys.argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					@tailwind base;
 | 
				
			||||||
 | 
					@tailwind components;
 | 
				
			||||||
 | 
					@tailwind utilities;
 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					/** @type {import('tailwindcss').Config} */
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  content: [
 | 
				
			||||||
 | 
					      './templates/**/*.html',
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  theme: {
 | 
				
			||||||
 | 
					    extend: {},
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  plugins: [],
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					{% load static %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<html lang="en" class="dark">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>Photo Grid with Tailwind CSS</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="{% static 'css/output.css' %}" type="text/css">
 | 
				
			||||||
 | 
					      <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
 | 
				
			||||||
 | 
					  <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
 | 
				
			||||||
 | 
					        document.documentElement.classList.add('dark');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        document.documentElement.classList.remove('dark')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					  <div class="text-sm text-gray-900">
 | 
				
			||||||
 | 
					      <nav class="flex items-center px-4 py-3 bg-white justify-between shadow">
 | 
				
			||||||
 | 
					          <div class="text-gray-900 space-x-6">
 | 
				
			||||||
 | 
					                <a class="font-bold flex items-center space-x-6" href="{% url 'courses:index' %} ">
 | 
				
			||||||
 | 
					                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
 | 
				
			||||||
 | 
					                    <path stroke-linecap="round" stroke-linejoin="round" d="M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0 1 20.25 6v12A2.25 2.25 0 0 1 18 20.25H6A2.25 2.25 0 0 1 3.75 18V6A2.25 2.25 0 0 1 6 3.75h1.5m9 0h-9" />
 | 
				
			||||||
 | 
					                </svg>
 | 
				
			||||||
 | 
					                    CourseField</a>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					            <div class="text-gray-900">
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					      </nav>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  {% block content %}
 | 
				
			||||||
 | 
					  {% endblock %}
 | 
				
			||||||
 | 
					  {% block scripts %}
 | 
				
			||||||
 | 
					  {% endblock %}
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,316 @@
 | 
				
			||||||
 | 
					<!doctype html>
 | 
				
			||||||
 | 
					{% load static %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<html lang="en" class="dark">
 | 
				
			||||||
 | 
					  <head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>Photo Grid with Tailwind CSS</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="{% static 'css/output.css' %}" type="text/css">
 | 
				
			||||||
 | 
					  <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
 | 
				
			||||||
 | 
					        document.documentElement.classList.add('dark');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        document.documentElement.classList.remove('dark')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </head>
 | 
				
			||||||
 | 
					  <body class="bg-gray-50 dark:bg-gray-800">
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					<header>
 | 
				
			||||||
 | 
					    <nav class="fixed z-30 w-full bg-white border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700 py-3 px-4">
 | 
				
			||||||
 | 
					        <div class="flex justify-between items-center max-w-screen-2xl mx-auto">
 | 
				
			||||||
 | 
					            <div class="flex justify-start items-center">
 | 
				
			||||||
 | 
					                <a href="/" class="flex mr-14">
 | 
				
			||||||
 | 
					                    <img src="https://flowbite-admin-dashboard.vercel.app/images/logo.svg" class="mr-3 h-8" alt="FlowBite Logo" />
 | 
				
			||||||
 | 
					                    <span class="self-center hidden sm:flex text-2xl font-semibold whitespace-nowrap dark:text-white">Flowbite</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="hidden justify-between items-center w-full lg:flex lg:w-auto lg:order-1">
 | 
				
			||||||
 | 
					                    <ul class="flex flex-col mt-4 space-x-6 text-sm font-medium lg:flex-row xl:space-x-8 lg:mt-0">
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block rounded text-primary-700 dark:text-primary-500" aria-current="page">Home</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white">Messages</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white">Profile</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block text-gray-700 hover:text-primary-700 dark:text-gray-400 dark:hover:text-white">Settings</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <button id="dropdownNavbarLink" data-dropdown-toggle="dropdownNavbar" class="flex justify-between items-center py-2 pr-4 pl-3 w-full font-medium text-gray-700 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-primary-700 md:p-0 md:w-auto dark:text-gray-400 dark:hover:text-white dark:focus:text-white dark:border-gray-700 dark:hover:bg-gray-700 md:dark:hover:bg-transparent">Dropdown <svg class="ml-1 w-4 h-4" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg></button>
 | 
				
			||||||
 | 
					                            
 | 
				
			||||||
 | 
					                            <div id="dropdownNavbar" class="hidden z-20 w-44 font-normal bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700 dark:divide-gray-600">
 | 
				
			||||||
 | 
					                                <ul class="py-1 text-sm text-gray-700 dark:text-gray-400" aria-labelledby="dropdownLargeButton">
 | 
				
			||||||
 | 
					                                  <li>
 | 
				
			||||||
 | 
					                                    <a href="#" class="block py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Item 1</a>
 | 
				
			||||||
 | 
					                                  </li>
 | 
				
			||||||
 | 
					                                  <li>
 | 
				
			||||||
 | 
					                                    <a href="#" class="block py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Item 2</a>
 | 
				
			||||||
 | 
					                                  </li>
 | 
				
			||||||
 | 
					                                  <li>
 | 
				
			||||||
 | 
					                                    <a href="#" class="block py-2 px-4 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Item 3</a>
 | 
				
			||||||
 | 
					                                  </li>
 | 
				
			||||||
 | 
					                                </ul>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="flex justify-between items-center lg:order-2">
 | 
				
			||||||
 | 
					                <div class="mr-3 -mb-1 hidden sm:block">
 | 
				
			||||||
 | 
					                    <a class="github-button" href="https://github.com/themesberg/flowbite-admin-dashboard" data-color-scheme="no-preference: dark; light: light; dark: light;" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star themesberg/flowbite-admin-dashboard on GitHub">Star</a>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <button type="button" data-dropdown-toggle="notification-dropdown" class="p-2 text-gray-500 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600">
 | 
				
			||||||
 | 
					                    <span class="sr-only">View notifications</span>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <svg aria-hidden="true" class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"></path></svg>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="hidden overflow-hidden z-50 my-4 max-w-sm text-base list-none bg-white rounded divide-y divide-gray-100 shadow-lg dark:divide-gray-600 dark:bg-gray-700" id="notification-dropdown">
 | 
				
			||||||
 | 
					                    <div class="block py-2 px-4 text-base font-medium text-center text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
 | 
				
			||||||
 | 
					                        Notifications
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div>
 | 
				
			||||||
 | 
					                    <a href="#" class="flex py-3 px-4 border-b hover:bg-gray-100 dark:hover:bg-gray-600 dark:border-gray-600">
 | 
				
			||||||
 | 
					                        <div class="flex-shrink-0">
 | 
				
			||||||
 | 
					                        <img class="w-11 h-11 rounded-full" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/bonnie-green.png" alt="Bonnie Green avatar">
 | 
				
			||||||
 | 
					                        <div class="flex absolute justify-center items-center ml-6 -mt-5 w-5 h-5 rounded-full border border-white bg-primary-700 dark:border-gray-700">
 | 
				
			||||||
 | 
					                            <svg aria-hidden="true" class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8.707 7.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l2-2a1 1 0 00-1.414-1.414L11 7.586V3a1 1 0 10-2 0v4.586l-.293-.293z"></path><path d="M3 5a2 2 0 012-2h1a1 1 0 010 2H5v7h2l1 2h4l1-2h2V5h-1a1 1 0 110-2h1a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V5z"></path></svg>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="pl-3 w-full">
 | 
				
			||||||
 | 
					                            <div class="text-gray-500 font-normal text-sm mb-1.5 dark:text-gray-400">New message from <span class="font-semibold text-gray-900 dark:text-white">Bonnie Green</span>: "Hey, what's up? All set for the presentation?"</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-primary-700 dark:text-primary-400">a few moments ago</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="flex py-3 px-4 border-b hover:bg-gray-100 dark:hover:bg-gray-600 dark:border-gray-600">
 | 
				
			||||||
 | 
					                        <div class="flex-shrink-0">
 | 
				
			||||||
 | 
					                        <img class="w-11 h-11 rounded-full" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/jese-leos.png" alt="Jese Leos avatar">
 | 
				
			||||||
 | 
					                        <div class="flex absolute justify-center items-center ml-6 -mt-5 w-5 h-5 bg-gray-900 rounded-full border border-white dark:border-gray-700">
 | 
				
			||||||
 | 
					                            <svg aria-hidden="true" class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"></path></svg>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="pl-3 w-full">
 | 
				
			||||||
 | 
					                            <div class="text-gray-500 font-normal text-sm mb-1.5 dark:text-gray-400"><span class="font-semibold text-gray-900 dark:text-white">Jese leos</span> and <span class="font-medium text-gray-900 dark:text-white">5 others</span> started following you.</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-primary-700 dark:text-primary-400">10 minutes ago</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="flex py-3 px-4 border-b hover:bg-gray-100 dark:hover:bg-gray-600 dark:border-gray-600">
 | 
				
			||||||
 | 
					                        <div class="flex-shrink-0">
 | 
				
			||||||
 | 
					                        <img class="w-11 h-11 rounded-full" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/joseph-mcfall.png" alt="Joseph McFall avatar">
 | 
				
			||||||
 | 
					                        <div class="flex absolute justify-center items-center ml-6 -mt-5 w-5 h-5 bg-red-600 rounded-full border border-white dark:border-gray-700">
 | 
				
			||||||
 | 
					                            <svg aria-hidden="true" class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="pl-3 w-full">
 | 
				
			||||||
 | 
					                            <div class="text-gray-500 font-normal text-sm mb-1.5 dark:text-gray-400"><span class="font-semibold text-gray-900 dark:text-white">Joseph Mcfall</span> and <span class="font-medium text-gray-900 dark:text-white">141 others</span> love your story. See it and view more stories.</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-primary-700 dark:text-primary-400">44 minutes ago</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="flex py-3 px-4 border-b hover:bg-gray-100 dark:hover:bg-gray-600 dark:border-gray-600">
 | 
				
			||||||
 | 
					                        <div class="flex-shrink-0">
 | 
				
			||||||
 | 
					                        <img class="w-11 h-11 rounded-full" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/roberta-casas.png" alt="Roberta Casas image">
 | 
				
			||||||
 | 
					                        <div class="flex absolute justify-center items-center ml-6 -mt-5 w-5 h-5 bg-green-400 rounded-full border border-white dark:border-gray-700">
 | 
				
			||||||
 | 
					                            <svg aria-hidden="true" class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 13V5a2 2 0 00-2-2H4a2 2 0 00-2 2v8a2 2 0 002 2h3l3 3 3-3h3a2 2 0 002-2zM5 7a1 1 0 011-1h8a1 1 0 110 2H6a1 1 0 01-1-1zm1 3a1 1 0 100 2h3a1 1 0 100-2H6z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="pl-3 w-full">
 | 
				
			||||||
 | 
					                            <div class="text-gray-500 font-normal text-sm mb-1.5 dark:text-gray-400"><span class="font-semibold text-gray-900 dark:text-white">Leslie Livingston</span> mentioned you in a comment: <span class="font-medium text-primary-700 dark:text-primary-500">@bonnie.green</span> what do you say?</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-primary-700 dark:text-primary-400">1 hour ago</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="flex py-3 px-4 hover:bg-gray-100 dark:hover:bg-gray-600">
 | 
				
			||||||
 | 
					                        <div class="flex-shrink-0">
 | 
				
			||||||
 | 
					                        <img class="w-11 h-11 rounded-full" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/avatars/robert-brown.png" alt="Robert image">
 | 
				
			||||||
 | 
					                        <div class="flex absolute justify-center items-center ml-6 -mt-5 w-5 h-5 bg-purple-500 rounded-full border border-white dark:border-gray-700">
 | 
				
			||||||
 | 
					                            <svg aria-hidden="true" class="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 6a2 2 0 012-2h6a2 2 0 012 2v8a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14.553 7.106A1 1 0 0014 8v4a1 1 0 00.553.894l2 1A1 1 0 0018 13V7a1 1 0 00-1.447-.894l-2 1z"></path></svg>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class="pl-3 w-full">
 | 
				
			||||||
 | 
					                            <div class="text-gray-500 font-normal text-sm mb-1.5 dark:text-gray-400"><span class="font-semibold text-gray-900 dark:text-white">Robert Brown</span> posted a new video: Glassmorphism - learn how to implement the new design trend.</div>
 | 
				
			||||||
 | 
					                            <div class="text-xs font-medium text-primary-700 dark:text-primary-400">3 hours ago</div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <a href="#" class="block py-2 text-base font-normal text-center text-gray-900 bg-gray-50 hover:bg-gray-100 dark:bg-gray-700 dark:text-white dark:hover:underline">
 | 
				
			||||||
 | 
					                        <div class="inline-flex items-center ">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mr-2 w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 12a2 2 0 100-4 2 2 0 000 4z"></path><path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        View all
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <button id="theme-toggle" data-tooltip-target="tooltip-toggle" type="button" class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
 | 
				
			||||||
 | 
					                    <svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path></svg>
 | 
				
			||||||
 | 
					                    <svg id="theme-toggle-light-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                <div id="tooltip-toggle" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
 | 
				
			||||||
 | 
					                    Toggle dark mode
 | 
				
			||||||
 | 
					                    <div class="tooltip-arrow" data-popper-arrow></div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <button type="button" data-dropdown-toggle="apps-dropdown" class="p-2 text-gray-500 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600">
 | 
				
			||||||
 | 
					                    <span class="sr-only">View notifications</span>
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path></svg>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="hidden overflow-hidden z-50 my-4 max-w-sm text-base list-none bg-white rounded divide-y divide-gray-100 shadow-lg dark:bg-gray-700 dark:divide-gray-600" id="apps-dropdown">
 | 
				
			||||||
 | 
					                    <div class="block py-2 px-4 text-base font-medium text-center text-gray-700 bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
 | 
				
			||||||
 | 
					                        Apps
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div class="grid grid-cols-3 gap-4 p-4">
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M10 2a4 4 0 00-4 4v1H5a1 1 0 00-.994.89l-1 9A1 1 0 004 18h12a1 1 0 00.994-1.11l-1-9A1 1 0 0015 7h-1V6a4 4 0 00-4-4zm2 5V6a2 2 0 10-4 0v1h4zm-6 3a1 1 0 112 0 1 1 0 01-2 0zm7-1a1 1 0 100 2 1 1 0 000-2z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Sales</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Users</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm0 2h10v7h-2l-1 2H8l-1-2H5V5z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Inbox</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-6-3a2 2 0 11-4 0 2 2 0 014 0zm-2 4a5 5 0 00-4.546 2.916A5.986 5.986 0 0010 16a5.986 5.986 0 004.546-2.084A5 5 0 0010 11z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Profile</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Settings</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M4 3a2 2 0 100 4h12a2 2 0 100-4H4z"></path><path fill-rule="evenodd" d="M3 8h14v7a2 2 0 01-2 2H5a2 2 0 01-2-2V8zm5 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Products</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M8.433 7.418c.155-.103.346-.196.567-.267v1.698a2.305 2.305 0 01-.567-.267C8.07 8.34 8 8.114 8 8c0-.114.07-.34.433-.582zM11 12.849v-1.698c.22.071.412.164.567.267.364.243.433.468.433.582 0 .114-.07.34-.433.582a2.305 2.305 0 01-.567.267z"></path><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-13a1 1 0 10-2 0v.092a4.535 4.535 0 00-1.676.662C6.602 6.234 6 7.009 6 8c0 .99.602 1.765 1.324 2.246.48.32 1.054.545 1.676.662v1.941c-.391-.127-.68-.317-.843-.504a1 1 0 10-1.51 1.31c.562.649 1.413 1.076 2.353 1.253V15a1 1 0 102 0v-.092a4.535 4.535 0 001.676-.662C13.398 13.766 14 12.991 14 12c0-.99-.602-1.765-1.324-2.246A4.535 4.535 0 0011 9.092V7.151c.391.127.68.317.843.504a1 1 0 101.511-1.31c-.563-.649-1.413-1.076-2.354-1.253V5z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Pricing</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 2a2 2 0 00-2 2v14l3.5-2 3.5 2 3.5-2 3.5 2V4a2 2 0 00-2-2H5zm2.5 3a1.5 1.5 0 100 3 1.5 1.5 0 000-3zm6.207.293a1 1 0 00-1.414 0l-6 6a1 1 0 101.414 1.414l6-6a1 1 0 000-1.414zM12.5 10a1.5 1.5 0 100 3 1.5 1.5 0 000-3z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Billing</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <a href="#" class="block p-4 text-center rounded-lg hover:bg-gray-100 dark:hover:bg-gray-600 group">
 | 
				
			||||||
 | 
					                        <svg aria-hidden="true" class="mx-auto mb-1 w-7 h-7 text-gray-400 group-hover:text-gray-500 dark:text-gray-400 dark:group-hover:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1"></path></svg>
 | 
				
			||||||
 | 
					                        <div class="text-sm text-gray-900 dark:text-white">Logout</div>
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                <button type="button" class="flex mx-3 text-sm bg-gray-800 rounded-full md:mr-0 flex-shrink-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" id="userMenuDropdownButton" aria-expanded="false" data-dropdown-toggle="userMenuDropdown">
 | 
				
			||||||
 | 
					                    <span class="sr-only">Open user menu</span>
 | 
				
			||||||
 | 
					                    <img class="w-8 h-8 rounded-full" src="https://flowbite.com/docs/images/people/profile-picture-5.jpg" alt="user photo">
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                <div class="hidden z-50 my-4 w-56 text-base list-none bg-white rounded divide-y divide-gray-100 shadow dark:bg-gray-700 dark:divide-gray-600" id="userMenuDropdown">
 | 
				
			||||||
 | 
					                    <div class="py-3 px-4">
 | 
				
			||||||
 | 
					                        <span class="block text-sm font-semibold text-gray-900 dark:text-white">Neil Sims</span>
 | 
				
			||||||
 | 
					                        <span class="block text-sm font-light text-gray-500 truncate dark:text-gray-400">name@flowbite.com</span>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="userMenuDropdownButton">
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white">My profile</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-400 dark:hover:text-white">Account settings</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                    <ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="userMenuDropdownButton">
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="flex items-center py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"><svg class="mr-2 w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd"></path></svg> My likes</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="flex items-center py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"><svg class="mr-2 w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M7 3a1 1 0 000 2h6a1 1 0 100-2H7zM4 7a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1zM2 11a2 2 0 012-2h12a2 2 0 012 2v4a2 2 0 01-2 2H4a2 2 0 01-2-2v-4z"></path></svg> Collections</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                    <ul class="py-1 font-light text-gray-500 dark:text-gray-400" aria-labelledby="dropdown">
 | 
				
			||||||
 | 
					                        <li>
 | 
				
			||||||
 | 
					                            <a href="#" class="block py-2 px-4 text-sm hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Sign out</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
 | 
					                    </ul>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					                <button type="button" id="toggleMobileMenuButton" data-collapse-toggle="toggleMobileMenu" class="items-center p-2 text-gray-500 rounded-lg md:ml-2 lg:hidden hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-700 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600">
 | 
				
			||||||
 | 
					                    <span class="sr-only">Open menu</span>
 | 
				
			||||||
 | 
					                    <svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"></path></svg>
 | 
				
			||||||
 | 
					                </button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					    <nav class="bg-white dark:bg-gray-900">
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <ul id="toggleMobileMenu" class="hidden flex-col mt-0 pt-16 w-full text-sm font-medium lg:hidden">
 | 
				
			||||||
 | 
					            <li class="block border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0" aria-current="page">Home</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li class="block border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Messages</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li class="block border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Profile</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li class="block border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Settings</a>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					            <li class="block border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                <button type="button" data-collapse-toggle="dropdownMobileNavbar" class="flex justify-between items-center py-3 px-4 w-full text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Dropdown <svg class="w-6 h-6 text-gray-500 dark:text-gray-400" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg></button>
 | 
				
			||||||
 | 
					                <ul id="dropdownMobileNavbar" class="hidden"> 
 | 
				
			||||||
 | 
					                    <li class="block border-t border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                        <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Item 1</a>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
 | 
					                    <li class="block border-b dark:border-gray-700">
 | 
				
			||||||
 | 
					                        <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Item 2</a>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
 | 
					                    <li class="block">
 | 
				
			||||||
 | 
					                        <a href="#" class="block py-3 px-4 text-gray-900 lg:py-0 dark:text-white lg:hover:underline lg:px-0">Item 3</a>
 | 
				
			||||||
 | 
					                    </li>
 | 
				
			||||||
 | 
					                </ul>
 | 
				
			||||||
 | 
					            </li>
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					    </nav>
 | 
				
			||||||
 | 
					  </header>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="flex pt-16 overflow-hidden bg-gray-50 dark:bg-gray-900">
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  <div id="main-content" class="relative w-full max-w-screen-2xl mx-auto h-full overflow-y-auto bg-gray-50 dark:bg-gray-900">
 | 
				
			||||||
 | 
					    <main>
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					<div class="px-4 pt-6 2xl:px-0">
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					    </main>
 | 
				
			||||||
 | 
					     <footer class="md:flex md:items-center md:justify-between px-4 2xl:px-0 py-6 md:py-10">
 | 
				
			||||||
 | 
					    <p class="text-sm text-center text-gray-500 mb-4 md:mb-0">
 | 
				
			||||||
 | 
					        © 2019-2023 <a href="https://flowbite.com//" class="hover:underline" target="_blank">Flowbite.com</a>. All rights reserved.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					    <ul class="flex flex-wrap items-center justify-center">
 | 
				
			||||||
 | 
					        <li><a href="#" class="mr-4 text-sm font-normal text-gray-500 hover:underline md:mr-6 dark:text-gray-400">Terms</a></li>
 | 
				
			||||||
 | 
					        <li><a href="#" class="mr-4 text-sm font-normal text-gray-500 hover:underline md:mr-6 dark:text-gray-400">Licensing</a></li>
 | 
				
			||||||
 | 
					        <li><a href="#" class="mr-4 text-sm font-normal text-gray-500 hover:underline md:mr-6 dark:text-gray-400">Cookie Policy</a></li>
 | 
				
			||||||
 | 
					        <li><a href="#" class="text-sm font-normal text-gray-500 hover:underline dark:text-gray-400">Contact</a></li>
 | 
				
			||||||
 | 
					    </ul>
 | 
				
			||||||
 | 
					</footer>
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					  {% block scripts %}
 | 
				
			||||||
 | 
					  {% endblock %}
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					{% extends 'courses/base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<div class="container mx-auto px-4 py-8">
 | 
				
			||||||
 | 
					    <div class="grid grid-cols-6 gap-4">
 | 
				
			||||||
 | 
					{% for lesson in lesson_list %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <!-- 课程卡片1 -->
 | 
				
			||||||
 | 
					      <div class="bg-white shadow-lg rounded-lg overflow-hidden max-w-sm hover:shadow-2xl">
 | 
				
			||||||
 | 
					      <a class="" href="{{ lesson.get_absolute_url }}">
 | 
				
			||||||
 | 
					        <img class="w-full h-48 object-cover" src="/{{ lesson.cover }}" alt="课程1图片">
 | 
				
			||||||
 | 
					        <div class="p-4">
 | 
				
			||||||
 | 
					          <h2 class="text-lg font-bold mb-2">{{ lesson.title }}</h2>
 | 
				
			||||||
 | 
					          <p class="text-gray-700 text-sm">课程描述信息...</p>
 | 
				
			||||||
 | 
					          <!-- 这里可以添加更多课程相关信息,如讲师、日期等 -->
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endfor %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					{% extends 'courses/base.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					<div class="container mx-auto px-4 py-8">
 | 
				
			||||||
 | 
					    <div class="grid grid-cols-6 gap-4">
 | 
				
			||||||
 | 
					{% for course in course_list %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <!-- 课程卡片1 -->
 | 
				
			||||||
 | 
					      <div class="bg-white shadow-lg rounded-lg overflow-hidden max-w-sm hover:shadow-2xl">
 | 
				
			||||||
 | 
					      <a class="" href="{{ course.get_absolute_url }}">
 | 
				
			||||||
 | 
					        <img class="w-full h-48 object-cover" src="https://dummyimage.com/421x261" alt="课程1图片">
 | 
				
			||||||
 | 
					        <div class="p-4">
 | 
				
			||||||
 | 
					          <h2 class="text-lg font-bold mb-2">{{ course.title }}</h2>
 | 
				
			||||||
 | 
					          <p class="text-gray-700 text-sm">课程描述信息...</p>
 | 
				
			||||||
 | 
					          <!-- 这里可以添加更多课程相关信息,如讲师、日期等 -->
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endfor %}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					{% extends 'courses/base.html' %}
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container px-4">
 | 
				
			||||||
 | 
					<h2 class="text-2xl font-bold">{{ lesson.title }}</h2>
 | 
				
			||||||
 | 
					    <div class="flex justify-center">
 | 
				
			||||||
 | 
					        <div class="w-5/6">
 | 
				
			||||||
 | 
					                <video id="player" playsinline controls >
 | 
				
			||||||
 | 
					                  <source src="{{ lesson.media.url }}" type="video/mp4" />
 | 
				
			||||||
 | 
					                </video>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{% block scripts %}
 | 
				
			||||||
 | 
					    <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					const player = new Plyr('#player');
 | 
				
			||||||
 | 
					// print current time on play every 10 seconds 
 | 
				
			||||||
 | 
					setInterval(() => {
 | 
				
			||||||
 | 
					    console.log('Current time:', player.currentTime);
 | 
				
			||||||
 | 
					}, 10000);
 | 
				
			||||||
 | 
					player.on('pause', event => {
 | 
				
			||||||
 | 
					    console.log('Video paused', player.currentTime);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue