diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 423a636..7e51457 100644 Binary files a/config/__pycache__/__init__.cpython-311.pyc and b/config/__pycache__/__init__.cpython-311.pyc differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..022afd4 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..2325870 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 9c49e09..30dd4f5 100644 Binary files a/config/__pycache__/wsgi.cpython-311.pyc and b/config/__pycache__/wsgi.cpython-311.pyc differ diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 74b1112..7cd10fe 100644 Binary files a/core/__pycache__/__init__.cpython-311.pyc and b/core/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..31ca66d 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 6f131d4..3b06638 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf223..3fa604c 100644 Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..ac20295 Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..e35281f 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..842f789 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..379fead 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..d537247 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,23 @@ from django.contrib import admin +from .models import Category, Channel, Video, LiveStream -# Register your models here. +@admin.register(Category) +class CategoryAdmin(admin.ModelAdmin): + list_display = ('name', 'slug') + prepopulated_fields = {'slug': ('name',)} + +@admin.register(Channel) +class ChannelAdmin(admin.ModelAdmin): + list_display = ('name', 'handle', 'user', 'created_at') + search_fields = ('name', 'handle') + +@admin.register(Video) +class VideoAdmin(admin.ModelAdmin): + list_display = ('title', 'channel', 'views', 'is_published', 'created_at') + list_filter = ('is_published', 'category') + search_fields = ('title', 'description') + +@admin.register(LiveStream) +class LiveStreamAdmin(admin.ModelAdmin): + list_display = ('title', 'channel', 'is_live', 'viewer_count') + list_filter = ('is_live',) \ No newline at end of file diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..75d3030 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,15 @@ +from django import forms +from .models import Video, Category + +class VideoUploadForm(forms.ModelForm): + class Meta: + model = Video + fields = ['title', 'description', 'thumbnail', 'video_file', 'category', 'video_type'] + widgets = { + 'title': forms.TextInput(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Enter a catchy title'}), + 'description': forms.Textarea(attrs={'class': 'form-control bg-dark text-white border-secondary', 'placeholder': 'Describe your cosmic content', 'rows': 4}), + 'category': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'video_type': forms.Select(attrs={'class': 'form-select bg-dark text-white border-secondary'}), + 'thumbnail': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}), + 'video_file': forms.FileInput(attrs={'class': 'form-control bg-dark text-white border-secondary'}), + } diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..4190988 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,76 @@ +# Generated by Django 5.2.7 on 2026-02-11 12:10 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + 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)), + ('slug', models.SlugField(blank=True, unique=True)), + ], + options={ + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='Channel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('handle', models.CharField(max_length=100, unique=True)), + ('thumbnail', models.ImageField(blank=True, null=True, upload_to='channels/thumbnails/')), + ('banner', models.ImageField(blank=True, null=True, upload_to='channels/banners/')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('subscribers', models.ManyToManyField(blank=True, related_name='subscriptions', to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='channel', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='LiveStream', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('thumbnail', models.ImageField(upload_to='live/thumbnails/')), + ('stream_key', models.CharField(default=uuid.uuid4, max_length=100, unique=True)), + ('is_live', models.BooleanField(default=False)), + ('viewer_count', models.PositiveIntegerField(default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='live_streams', to='core.channel')), + ], + ), + migrations.CreateModel( + name='Video', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('thumbnail', models.ImageField(upload_to='videos/thumbnails/')), + ('video_file', models.FileField(blank=True, null=True, upload_to='videos/raw/')), + ('hls_url', models.URLField(blank=True, help_text='S3/CloudFront HLS URL', null=True)), + ('views', models.PositiveIntegerField(default=0)), + ('is_published', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='videos', to='core.category')), + ('channel', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='videos', to='core.channel')), + ], + options={ + 'ordering': ['-created_at'], + }, + ), + ] diff --git a/core/migrations/0002_video_video_type.py b/core/migrations/0002_video_video_type.py new file mode 100644 index 0000000..8ad30a3 --- /dev/null +++ b/core/migrations/0002_video_video_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-02-11 12:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='video', + name='video_type', + field=models.CharField(choices=[('standard', 'Standard'), ('short', 'Short'), ('live_recording', 'Live Recording')], default='standard', max_length=20), + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..0931d69 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_video_video_type.cpython-311.pyc b/core/migrations/__pycache__/0002_video_video_type.cpython-311.pyc new file mode 100644 index 0000000..3064806 Binary files /dev/null and b/core/migrations/__pycache__/0002_video_video_type.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 9c833c8..8956bb3 100644 Binary files a/core/migrations/__pycache__/__init__.cpython-311.pyc and b/core/migrations/__pycache__/__init__.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..9d9a14b 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,72 @@ from django.db import models +from django.contrib.auth.models import User +from django.utils.text import slugify +import uuid -# Create your models here. +class Category(models.Model): + name = models.CharField(max_length=100) + slug = models.SlugField(unique=True, blank=True) + + def save(self, *args, **kwargs): + if not self.slug: + self.slug = slugify(self.name) + super().save(*args, **kwargs) + + class Meta: + verbose_name_plural = "Categories" + + def __str__(self): + return self.name + +class Channel(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='channel') + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + handle = models.CharField(max_length=100, unique=True) + thumbnail = models.ImageField(upload_to='channels/thumbnails/', blank=True, null=True) + banner = models.ImageField(upload_to='channels/banners/', blank=True, null=True) + subscribers = models.ManyToManyField(User, related_name='subscriptions', blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + +class Video(models.Model): + TYPE_CHOICES = ( + ('standard', 'Standard'), + ('short', 'Short'), + ('live_recording', 'Live Recording'), + ) + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name='videos') + title = models.CharField(max_length=255) + description = models.TextField(blank=True) + thumbnail = models.ImageField(upload_to='videos/thumbnails/') + video_file = models.FileField(upload_to='videos/raw/', blank=True, null=True) + hls_url = models.URLField(blank=True, null=True, help_text="S3/CloudFront HLS URL") + views = models.PositiveIntegerField(default=0) + category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='videos') + video_type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='standard') + is_published = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['-created_at'] + + def __str__(self): + return self.title + +class LiveStream(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + channel = models.ForeignKey(Channel, on_delete=models.CASCADE, related_name='live_streams') + title = models.CharField(max_length=255) + description = models.TextField(blank=True) + thumbnail = models.ImageField(upload_to='live/thumbnails/') + stream_key = models.CharField(max_length=100, unique=True, default=uuid.uuid4) + is_live = models.BooleanField(default=False) + viewer_count = models.PositiveIntegerField(default=0) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.channel.name} - {self.title}" diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..3128f3e 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,96 @@ +{% load static %} -
- -