initial commit

This commit is contained in:
2021-02-24 20:13:54 -08:00
commit dfeca6a325
50 changed files with 1467 additions and 0 deletions

0
audio/__init__.py Normal file
View File

3
audio/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
audio/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class AudioConfig(AppConfig):
name = 'audio'

92
audio/audiorssfeed.py Normal file
View File

@@ -0,0 +1,92 @@
from django.contrib.syndication.views import Feed as RSSFeed
from django.contrib.sites.shortcuts import get_current_site
from django.urls import reverse
from django.utils.feedgenerator import Rss201rev2Feed
from tp.settings import IMAGES_URL, MP3_URL
from .models import Feed
from datetime import datetime
class AudioRssFeedGenerator(Rss201rev2Feed):
content_type = 'application/xml; charset=utf-8'
def add_root_elements(self, handler):
super().add_root_elements(handler)
handler.startElement("image", {})
handler.addQuickElement("url", self.feed['image_url'])
handler.addQuickElement("title", self.feed['image_title'])
handler.addQuickElement("link", self.feed['image_link'])
handler.addQuickElement("description", self.feed['image_desc'])
handler.endElement("image")
def add_item_elements(self, handler, item):
super().add_item_elements(handler, item)
handler.startElement("image", {})
handler.addQuickElement("url", item['image_url'])
handler.addQuickElement("title", item['image_title'])
handler.addQuickElement("link", item['image_link'])
handler.addQuickElement("description", item['image_desc'])
handler.endElement("image")
class AudioRssFeed(RSSFeed):
feed_type = AudioRssFeedGenerator
def get_object(self, request, slug):
obj = Feed.objects.get(slug=slug)
obj.request = request
return obj
def items(self, obj):
xr = [x for x in obj.episode_set.order_by('pub_date')]
for x in xr:
x.request = obj.request
return xr
def item_enclosure_url(self, item):
return f'{MP3_URL}{item.mp3}'
def item_enclosure_length(self, item):
return item.image.size
def item_enclosure_mime_type(self, item):
return "audio/mpeg"
def item_pubdate(self, item):
'''
Need to return datetime.datetime object,
but item.pub_date is an datetime.date object
'''
return datetime.fromisoformat(item.pub_date.isoformat())
def link(self, obj):
return reverse('audio:feed', kwargs={'pk': obj.pk, 'slug': obj.slug})
def title(self, obj):
return obj.title
def description(self, obj):
return obj.description
def item_link(self, item):
return reverse('audio:episode', kwargs={'pk': item.pk, 'slug': item.slug})
def item_title(self, item):
return f'{item.episode_number}: {item.title}'
def item_extra_kwargs(self, item):
x = {}
x['image_url'] = f'{IMAGES_URL}{item.image.name}'
x['image_title'] = item.title
x['image_link'] = f'{get_current_site(item.request)}{self.item_link(item)}'
x['image_desc'] = f'Image for: {item.title}'
return x
def feed_extra_kwargs(self, obj):
x = {}
x['image_url'] = f'{IMAGES_URL}{obj.image.name}'
x['image_title'] = obj.title
x['image_link'] = f'{get_current_site(obj.request)}{self.link(obj)}'
x['image_desc'] = f'Image for: {obj.title}'
return x

54
audio/episode_views.py Normal file
View File

@@ -0,0 +1,54 @@
from django.shortcuts import render, redirect
from .forms import EpisodeForm
from .models import Feed, Episode
def edit_episode(request, pk, title_slug):
if not request.user.is_authenticated:
return redirect('audio:home')
episode = Episode.objects.get(id=pk)
if not episode.user == request.user:
return redirect('audio:home')
if request.method == "POST":
form = EpisodeForm(request.POST, request.FILES, instance=episode)
if form.is_valid():
form.save()
return redirect('audio:home')
else:
form = EpisodeForm(instance=episode)
return render(
request, 'base_form.html',
{
'form': form,
'heading': 'Edit Episode?',
'title': 'Edit Episode?',
'submit': 'save',
'form_data': 'TRUE',
})
def new_episode(request, feed_pk, feed_title_slug):
if not request.user.is_authenticated:
return redirect('audio:home')
feed = Feed.objects.get(id=feed_pk)
if not feed.user == request.user:
return redirect('audio:home')
if request.method == "POST":
form = EpisodeForm(request.POST, request.FILES)
if form.is_valid():
episode = form.save(commit=False)
episode.user = request.user
episode.feed = feed
episode.save()
return redirect('audio:new_feed')
else:
form = EpisodeForm()
return render(
request, 'base_form.html',
{
'form': form,
'heading': 'New Episode?',
'title': 'New Episode?',
'submit': 'submit',
'form_data': 'TRUE',
})

24
audio/forms.py Normal file
View File

@@ -0,0 +1,24 @@
from .models import Feed, Episode
from django import forms
class FeedForm(forms.ModelForm):
class Meta:
model = Feed
fields = [
'title', 'author', 'description', 'image'
]
class EpisodeForm(forms.ModelForm):
pub_date = forms.DateField(
widget=forms.TextInput(attrs={'type': 'date'})
)
class Meta:
model = Episode
fields = [
'title', 'author', 'pub_date', 'episode_number', 'description', 'image', 'mp3'
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 3.1.6 on 2021-02-22 06:44
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Feed',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_on', models.DateTimeField(auto_now_add=True)),
('title', models.CharField(max_length=120)),
('author', models.CharField(max_length=120)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 3.1.6 on 2021-02-22 06:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audio', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='feed',
name='slug',
field=models.SlugField(max_length=255, null=True, unique=True),
),
migrations.AlterField(
model_name='feed',
name='author',
field=models.CharField(max_length=255),
),
migrations.AlterField(
model_name='feed',
name='title',
field=models.CharField(max_length=255),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.6 on 2021-02-22 06:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audio', '0002_auto_20210221_2256'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='slug',
field=models.SlugField(max_length=255, unique=True),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-02-22 21:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audio', '0003_auto_20210221_2256'),
]
operations = [
migrations.AddField(
model_name='feed',
name='description',
field=models.TextField(default=''),
preserve_default=False,
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-02-23 00:02
from django.db import migrations, models
import tp.storage_backends
class Migration(migrations.Migration):
dependencies = [
('audio', '0004_feed_description'),
]
operations = [
migrations.AddField(
model_name='feed',
name='image',
field=models.ImageField(null=True, storage=tp.storage_backends.PublicImageStorage(), upload_to=''),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-02-23 00:03
from django.db import migrations, models
import tp.storage_backends
class Migration(migrations.Migration):
dependencies = [
('audio', '0005_feed_image'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='image',
field=models.ImageField(blank=True, null=True, storage=tp.storage_backends.PublicImageStorage(), upload_to=''),
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 3.1.6 on 2021-02-23 05:00
import audio.models
from django.db import migrations, models
import tp.storage_backends
class Migration(migrations.Migration):
dependencies = [
('audio', '0006_auto_20210222_1603'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='image',
field=models.ImageField(blank=True, null=True, storage=tp.storage_backends.PublicImageStorage(), upload_to=audio.models.slugify_file_name),
),
]

View File

@@ -0,0 +1,42 @@
# Generated by Django 3.1.6 on 2021-02-23 08:18
import audio.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import tp.storage_backends
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('audio', '0007_auto_20210222_2100'),
]
operations = [
migrations.AlterField(
model_name='feed',
name='image',
field=models.ImageField(blank=True, null=True, storage=tp.storage_backends.PublicImageStorage(), upload_to=audio.models.slugify_file_name),
),
migrations.CreateModel(
name='Episode',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('title', models.CharField(max_length=255)),
('author', models.CharField(max_length=255)),
('slug', models.SlugField(max_length=255, unique=True)),
('description', models.TextField()),
('created_on', models.DateTimeField(auto_now_add=True)),
('image', models.ImageField(blank=True, null=True, storage=tp.storage_backends.PublicImageStorage(), upload_to=audio.models.slugify_file_name)),
('mp3', models.FileField(blank=True, null=True, storage=tp.storage_backends.PublicMP3Storage(), upload_to=audio.models.slugify_file_name)),
('feed', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='audio.feed')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.1.6 on 2021-02-23 08:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audio', '0008_auto_20210223_0018'),
]
operations = [
migrations.AddField(
model_name='episode',
name='pub_date',
field=models.DateField(default=None),
preserve_default=False,
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.7 on 2021-02-24 01:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('audio', '0009_episode_pub_date'),
]
operations = [
migrations.AddField(
model_name='episode',
name='episode_number',
field=models.IntegerField(null=True),
),
]

View File

65
audio/models.py Normal file
View File

@@ -0,0 +1,65 @@
from django.db import models
from tp.models import UUIDAsIDModel
from django.contrib.auth.models import User
from django.utils.text import slugify
from tp.storage_backends import PublicImageStorage, PublicMP3Storage
import string, random
def rand_slug():
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8))
def slugify_file_name(instance, filename):
fname, dot, extension = filename.rpartition('.')
slug = slugify(fname)
return f'{slug}.{extension}'
class Feed(UUIDAsIDModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
author = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
description = models.TextField(null=False)
image = models.ImageField(
storage=PublicImageStorage(),
upload_to=slugify_file_name,
null=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(rand_slug() + "-" + self.title)
super(Feed, self).save(*args, **kwargs)
def __str__(self):
return str(self.title)
class Episode(UUIDAsIDModel):
user = models.ForeignKey(User, on_delete=models.CASCADE)
feed = models.ForeignKey(Feed, on_delete=models.CASCADE)
title = models.CharField(max_length=255)
author = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, unique=True)
description = models.TextField(null=False)
created_on = models.DateTimeField(auto_now_add=True)
pub_date = models.DateField()
episode_number = models.IntegerField(null=True)
image = models.ImageField(
storage=PublicImageStorage(),
upload_to=slugify_file_name,
null=True, blank=True)
mp3 = models.FileField(
storage=PublicMP3Storage(),
upload_to=slugify_file_name,
null=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(rand_slug() + "-" + self.title)
super(Episode, self).save(*args, **kwargs)
def __str__(self):
return str(self.title)

View File

@@ -0,0 +1,54 @@
{% extends "base.html" %}
{% block content %}
{% include "base_navbar.html" %}
{% include "base_heading.html" %}
<div id="main" class="mx-0 px-0">
<div class="row w-100 mx-0">
<div class="col-0 col-sm-3">
</div>
<div class="col-12 col-sm-6 px-0 mx-0">
{% for i in feeds %}
<div class="card mx-1 mb-3">
<h3 class="text-center mt-2">
{{ i.title }}
</h3>
<div class="container w-100 mb-2">
<div class="row">
<div class="col-3 d-flex flex-column justify-content-center">
<img src="{{ IMAGES_URL }}{{ i.image }}">
</div>
<div class="col-9 d-flex flex-row justify-content-center">
<div class="d-flex flex-column justify-content-center">
<p>{{ i.created_on }}</p>
<p><a href="{% url 'audio:feed' pk=i.pk slug=i.slug %}">{{ i.title }}</a></p>
<p><a href="{% url 'audio:rss' slug=i.slug %}">RSS</a></p>
<p>{{ i.description }}</p>
</div>
</div>
</div>
</div>
{% if user.is_authenticated %}
{% if user == i.user %}
<div class="container w-100 d-flex justify-content-around my-1">
<div>
<a href="{% url 'audio:edit_feed' pk=i.pk title_slug=i.slug %}">Edit Feed?</a>
</div>
<div>
<a href="{% url 'audio:new_episode' feed_pk=i.pk feed_title_slug=i.slug %}">New Episode?</a>
</div>
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
<div class="col-0 col-sm-3">
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,87 @@
{% extends "base.html" %}
{% block content %}
{% include "base_navbar.html" %}
{% include "base_heading.html" %}
<div id="main" class="mx-0 px-0">
<div class="row w-100 mx-0">
<div class="col-0 col-sm-3">
</div>
<div class="col-12 col-sm-6 px-0 mx-0">
{% for j in episodes %}
<div class="card mx-1 mb-3">
<h3 class="text-center mt-2">
{{ j.episode_number }}. {{ j.title }}
</h3>
<div class="container w-100 mb-2">
<div class="row">
<div class="col-3 d-flex flex-column justify-content-center">
<img src="{{ IMAGES_URL }}{{ j.image }}">
</div>
<div class="col-9 d-flex flex-row justify-content-center">
<div class="d-flex flex-column justify-content-center">
<p>{{ j.pub_date }}</p>
<p><a href="{% url 'audio:episode' pk=j.pk slug=j.slug %}">{{ j.title }}</a></p>
<p><a href="{% url 'audio:rss' slug=j.feed.slug %}">RSS</a></p>
<p>{{ j.description }}</p>
</div>
</div>
</div>
</div>
<div class="container w-100 mb-2 text-center">
<audio controls class="btn btn-dark w-100">
<source src="{{ MP3_URL }}{{ j.mp3 }}" type="audio/mpeg">
Your browser does not support the audio tag.
</audio>
</div>
{% if user.is_authenticated %}
{% if user == j.user %}
<div class="container w-100 d-flex justify-content-around my-1">
<div>
<a href="{% url 'audio:edit_episode' pk=j.pk title_slug=j.slug %}">Edit Episode?</a><br>
</div>
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
<div class="col-0 col-sm-3">
</div>
</div>
</div>
{% endblock %}
{% for j in episodes %}
<div class="container text-center">
<h3>
{{ j.title }}
</h3>
<a href="{% url 'audio:episode' pk=j.pk slug=j.slug %}">{{ j.title }}</a>
<p>
{{ j.pub_date }}
</p>
<p>
{{ j.description }}
</p>
<div class="container w-25 h-25">
<img class="w-50 h-50" src="{{ IMAGES_URL }}{{ j.image }}">
</div>
<a href="{% url 'audio:rss' slug=j.feed.slug %}">RSS</a><br>
<audio controls class="btn btn-dark">
<source src="{{ MP3_URL }}{{ j.mp3 }}" type="audio/mpeg">
Your browser does not support the audio tag.
</audio>
{% if user.is_authenticated %}
{% if user == j.user %}
<div class="container w-25 h-25 mt-3">
<a href="{% url 'audio:edit_episode' pk=j.pk title_slug=j.slug %}">Edit Episode?</a><br>
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}

3
audio/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

18
audio/urls.py Normal file
View File

@@ -0,0 +1,18 @@
from django.urls import path
from . import views
from .episode_views import new_episode, edit_episode
from .audiorssfeed import AudioRssFeed
app_name = "audio"
urlpatterns = [
path('', views.home, name='home'),
path('new-feed/', views.new_feed, name='new_feed'),
path('feeds/', views.feeds, name='feeds'),
path('edit-feed/<str:pk>/<str:title_slug>', views.edit_feed, name='edit_feed'),
path('new-episode/<str:feed_pk>/<str:feed_title_slug>', new_episode, name='new_episode'),
path('edit-episode/<str:pk>/<str:title_slug>', edit_episode, name='edit_episode'),
path('rss/<str:slug>.xml', AudioRssFeed(), name='rss'),
path('feed/<str:pk>/<str:slug>', views.feed, name='feed'),
path('episode/<str:pk>/<str:slug>', views.episode, name='episode'),
]

80
audio/views.py Normal file
View File

@@ -0,0 +1,80 @@
from django.shortcuts import render, redirect
from .forms import FeedForm
from .models import Feed, Episode
from tp.settings import IMAGES_URL, MP3_URL
def home(request):
episodes = Episode.objects.all()
return render(
request,
'audio/index.html',
{'episodes': episodes, 'IMAGES_URL': IMAGES_URL, 'MP3_URL': MP3_URL})
def feed(request, pk, slug):
feed = Feed.objects.get(id=pk)
episodes = feed.episode_set.all()
return render(
request, 'audio/index.html',
{
'episodes': episodes, 'IMAGES_URL': IMAGES_URL,
'MP3_URL': MP3_URL, 'title': feed.title, 'heading': feed.title
})
def episode(request, pk, slug):
episode = Episode.objects.get(id=pk)
return render(
request, 'audio/index.html',
{
'episodes': (episode, ), 'IMAGES_URL': IMAGES_URL,
'MP3_URL': MP3_URL, 'title': episode.title, 'heading': episode.title
})
def feeds(request):
feeds = Feed.objects.all().order_by('-created_on')
return render(
request,
'audio/feeds.html',
{'feeds': feeds, 'IMAGES_URL': IMAGES_URL})
def new_feed(request):
if not request.user.is_authenticated:
return redirect('audio:home')
if request.method == "POST":
form = FeedForm(request.POST, request.FILES)
if form.is_valid():
feed = form.save(commit=False)
feed.user = request.user
feed.save()
return redirect('audio:new_feed')
else:
form = FeedForm()
return render(request, 'base_form.html', {'form': form})
def edit_feed(request, pk, title_slug):
if not request.user.is_authenticated:
return redirect('audio:home')
feed = Feed.objects.get(id=pk)
if not feed.user == request.user:
return redirect('audio:home')
if request.method == "POST":
form = FeedForm(request.POST, request.FILES, instance=feed)
if form.is_valid():
feed.save()
return redirect('audio:new_feed')
else:
form = FeedForm(instance=feed)
return render(
request, 'base_form.html',
{
'form': form,
'heading': 'Edit Feed?',
'title': 'Edit Feed?',
'submit': 'save',
'form_data': 'TRUE',
})