diff --git a/app/item/admin.py b/app/item/admin.py index 94acd42..3d41227 100644 --- a/app/item/admin.py +++ b/app/item/admin.py @@ -9,6 +9,10 @@ class ItemAdmin(admin.ModelAdmin): list_filter = ( ("published", admin.EmptyFieldListFilter), ) + raw_id_fields = ['user'] + + def get_changeform_initial_data(self, request): + return {'user': request.user} admin.site.register(models.Item, ItemAdmin) @@ -25,5 +29,6 @@ class CommentAdmin(admin.ModelAdmin): list_filter = ( ("published", admin.EmptyFieldListFilter), ) + raw_id_fields = ['item', 'user'] admin.site.register(models.Comment, CommentAdmin) diff --git a/app/item/models.py b/app/item/models.py index 9584121..de681d0 100644 --- a/app/item/models.py +++ b/app/item/models.py @@ -26,14 +26,14 @@ class Item(models.Model): created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) - user = models.ForeignKey(User, null=True, related_name='items', on_delete=models.CASCADE) - url = models.CharField(max_length=1024, unique=True) title = models.CharField(max_length=1024) - description = models.TextField(default="", blank=True) + url = models.CharField(max_length=1024, unique=True) + description = models.TextField(default="", blank=True, editable=False) published = models.DateTimeField(default=timezone.now, null=True, blank=True) - announced = models.DateTimeField(null=True, default=None, blank=True) + announced = models.DateTimeField(null=True, default=None, blank=True, editable=False) data = models.JSONField(default=dict, editable=False) + user = models.ForeignKey(User, null=True, related_name='items', on_delete=models.CASCADE) def save(self, *args, **kwargs): if self.url and not self.data: @@ -105,14 +105,16 @@ class Comment(models.Model): modified = models.DateTimeField(auto_now=True) item = models.ForeignKey(Item, related_name='comments', on_delete=models.CASCADE) - user = models.ForeignKey(User, null=True, related_name='comments', on_delete=models.CASCADE, blank=True) - session_key = models.CharField(max_length=60, null=True, default=None, blank=True) + text = models.TextField(default="") + + name = models.CharField(max_length=1024, blank=True) + email = models.CharField(max_length=1024, blank=True) + + user = models.ForeignKey(User, null=True, related_name='comments', on_delete=models.CASCADE, blank=True) + session_key = models.CharField(max_length=60, null=True, default=None, blank=True, editable=False) - name = models.CharField(max_length=1024) - email = models.CharField(max_length=1024) - text = models.TextField(default="", blank=True) data = models.JSONField(default=dict, editable=False) - published = models.DateTimeField(null=True, default=None) + published = models.DateTimeField(null=True, default=None, blank=True) class Meta: permissions = [ diff --git a/app/item/tasks.py b/app/item/tasks.py index 7f47746..b6562c2 100644 --- a/app/item/tasks.py +++ b/app/item/tasks.py @@ -12,7 +12,7 @@ from ..celery import app from . import models -@app.task(queue="default") +@app.task(queue="default", ignore_results=True) def announce_items(): if not getattr(settings, 'SIGNAL_ANNOUNCE_GROUP'): return @@ -42,18 +42,27 @@ def announce_items(): os.unlink(f.name) -@app.task(queue="default") +@app.task(queue="default", ignore_results=True) def notify_moderators(id, link): comment = models.Comment.objects.filter(id=id).first() if comment: - message = "%s commented on %s (%s)\n\n%s" % (comment.name, comment.item.title, link, comment.text) + message = "%s commented on %s (%s)\n\n%s" % ( + comment.name, comment.item.title, link, comment.text + ) r = rpc.send(message, group=settings.SIGNAL_MODERATORS_GROUP) if r and "timestamp" in r: comment.data["moderator_ts"] = r["timestamp"] comment.save() + if comment.published: + account = settings.SIGNAL_ACCOUNT + group = settings.SIGNAL_MODERATORS_GROUP + rpc.send_reaction( + account, comment.data["moderator_ts"], "🎉", group=group + ) else: print("failed to notify", r) + @app.on_after_finalize.connect def setup_periodic_tasks(sender, **kwargs): sender.add_periodic_task(crontab(minute="*/2"), announce_items.s()) diff --git a/app/item/views.py b/app/item/views.py index 8c993a3..f216d9c 100644 --- a/app/item/views.py +++ b/app/item/views.py @@ -1,3 +1,5 @@ +import xml.etree.ElementTree as ET + from datetime import date, datetime, timedelta import json @@ -6,6 +8,7 @@ from django.shortcuts import render from django.db.models import Q from django.utils.html import mark_safe from django.conf import settings +from django.http import HttpResponse from . import models from . import tasks @@ -14,7 +17,7 @@ from .utils import render_to_json def index(request): - context = {} + context = {"settings": settings} week, archive = models.Item.public() context['items'] = week context['archive'] = archive.exists() @@ -22,7 +25,7 @@ def index(request): def archive(request): - context = {} + context = {"settings": settings} qs = models.Item.public() week, archive = models.Item.public() context['items'] = archive @@ -30,7 +33,7 @@ def archive(request): def item(request, id): - context = {} + context = {"settings": settings} item = models.Item.objects.get(id=id) context['item'] = item qs = item.comments.order_by('created') @@ -71,9 +74,8 @@ def comment(request): comment.session_key = request.session.session_key comment.text = data['text'] comment.save() - if not comment.published: - link = request.build_absolute_uri(comment.item.get_absolute_url()) - tasks.notify_moderators.delay(comment.id, link) + link = request.build_absolute_uri(comment.item.get_absolute_url()) + tasks.notify_moderators.delay(comment.id, link) response = comment.json() return render_to_json(response) @@ -88,8 +90,61 @@ def publish_comment(request): if comment.data.get("moderator_ts"): account = settings.SIGNAL_ACCOUNT group = settings.SIGNAL_MODERATORS_GROUP - send_reaction(account, comment.data["moderator_ts"], "🎉", group=group) + send_reaction( + account, comment.data["moderator_ts"], "🎉", group=group + ) response['status'] = 'ok' else: response['error'] = 'permission denied' return render_to_json(response) + + +def atom_xml(request): + feed = ET.Element("feed") + feed.attrib['xmlns'] = 'http://www.w3.org/2005/Atom' + feed.attrib['xmlns:media'] = 'http://search.yahoo.com/mrss/' + feed.attrib['xml:lang'] = 'en' + title = ET.SubElement(feed, "title") + title.text = settings.SITENAME + title.attrib['type'] = 'text' + link = ET.SubElement(feed, "link") + link.attrib['rel'] = 'self' + link.attrib['type'] = 'application/atom+xml' + atom_link = request.build_absolute_uri('/atom.xml') + link.attrib['href'] = atom_link + el = ET.SubElement(feed, 'id') + el.text = atom_link + + week, archive = models.Item.public() + for item in week: + page_link = request.build_absolute_uri(item.get_absolute_url()) + + entry = ET.Element("entry") + title = ET.SubElement(entry, "title") + title.text = item.title + link = ET.SubElement(entry, "link") + link.attrib['rel'] = 'alternate' + link.attrib['href'] = page_link + updated = ET.SubElement(entry, "updated") + updated.text = item.modified.strftime("%Y-%m-%dT%H:%M:%SZ") + published = ET.SubElement(entry, "published") + published.text = item.created.strftime("%Y-%m-%dT%H:%M:%SZ") + el = ET.SubElement(entry, "id") + el.text = page_link + print(item.data) + if 'title' in item.data and 'thumbnail' in item.data: + html = f''' +
+ {item.data['title']} +
+ + '''.strip() + content = ET.SubElement(entry, "content") + content.attrib['type'] = 'html' + content.text = html + feed.append(entry) + return HttpResponse( + '\n' + ET.tostring(feed).decode(), + 'application/atom+xml' + ) + diff --git a/app/page/__init__.py b/app/page/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/page/admin.py b/app/page/admin.py new file mode 100644 index 0000000..d38f4ce --- /dev/null +++ b/app/page/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from . import models + +@admin.decorators.register(models.Page) +class PageAdmin(admin.ModelAdmin): + list_display = ('slug', 'title', 'created', 'modified') + search_fields = ['title', 'slug'] diff --git a/app/page/apps.py b/app/page/apps.py new file mode 100644 index 0000000..77a4bc1 --- /dev/null +++ b/app/page/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class PageConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "app.page" diff --git a/app/page/migrations/0001_initial.py b/app/page/migrations/0001_initial.py new file mode 100644 index 0000000..f3a7a00 --- /dev/null +++ b/app/page/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.3 on 2023-07-25 18:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Page", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created", models.DateTimeField(auto_now_add=True)), + ("modified", models.DateTimeField(auto_now=True)), + ( + "slug", + models.CharField(max_length=255, unique=True, verbose_name="Slug"), + ), + ("title", models.CharField(max_length=1024)), + ("content", models.TextField(blank=True, default="")), + ], + ), + ] diff --git a/app/page/migrations/__init__.py b/app/page/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/page/models.py b/app/page/models.py new file mode 100644 index 0000000..603a7bb --- /dev/null +++ b/app/page/models.py @@ -0,0 +1,16 @@ +from django.db import models + + +class Page(models.Model): + created = models.DateTimeField(auto_now_add=True) + modified = models.DateTimeField(auto_now=True) + slug = models.CharField('Slug', max_length=255, unique=True) + + title = models.CharField(max_length=1024) + content = models.TextField(default='', blank=True) + + def __str__(self): + return self.slug + + def get_absolute_url(self): + return '/%s' % self.slug diff --git a/app/page/tests.py b/app/page/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/app/page/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/app/page/views.py b/app/page/views.py new file mode 100644 index 0000000..7c3da49 --- /dev/null +++ b/app/page/views.py @@ -0,0 +1,13 @@ +from django.conf import settings +from django.shortcuts import render, redirect, get_object_or_404 + +from . import models + + +def page(request, slug): + page = get_object_or_404(models.Page, slug=slug) + context = { + 'settings': settings, + 'page': page, + } + return render(request, 'page.html', context) diff --git a/app/settings/common.py b/app/settings/common.py index 32d240d..8c7a9cd 100644 --- a/app/settings/common.py +++ b/app/settings/common.py @@ -60,6 +60,7 @@ INSTALLED_APPS = [ "app.user", "app.item", "app.signalbot", + "app.page", ] MIDDLEWARE = [ @@ -86,6 +87,9 @@ TEMPLATES = [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], + "builtins": [ + "django.templatetags.static", + ] }, }, ] @@ -159,3 +163,5 @@ CELERY_RESULT_BACKEND = 'django-db' SIGNAL_MODERATORS = [] RATELIMIT_CACHE_BACKEND = "app.brake_backend.BrakeBackend" + +SITENAME = "phantas.ma" diff --git a/app/static/css/site.scss b/app/static/css/site.scss index 4a8e4da..948c32b 100644 --- a/app/static/css/site.scss +++ b/app/static/css/site.scss @@ -50,3 +50,24 @@ header { } } +.burger { + cursor: pointer; + padding-left: 4px; +} + +nav.overlay { + position: absolute; + width: 100%; + height: 100vh; + top: 42px; + left: 0; + background: rgb(16, 16, 16); + opacity: 0; + z-index: 100; + padding: 4px; + display: none; + &.active { + display: block; + opacity: 0.9; + } +} diff --git a/app/static/css/style.css b/app/static/css/style.css index 61dbb57..27dc2a7 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -35,7 +35,7 @@ video, .poster { .content { display: flex; flex-direction: column; - min-height: 100vh; + min-height: max(100vh, 100%); max-width: 1000px; margin: auto; } diff --git a/app/static/js/utils.js b/app/static/js/utils.js index d77024f..cbaec6a 100644 --- a/app/static/js/utils.js +++ b/app/static/js/utils.js @@ -155,6 +155,9 @@ const getVideoURL = function(id, resolution, part, track, streamId) { .replace('{resolution}', resolution) .replace('{uid}', uid) .replace('{uid42}', uid % 42); + if (!prefix) { + prefix = pandoraURL + } return prefix + '/' + getVideoURLName(id, resolution, part, track, streamId); }; diff --git a/app/templates/base.html b/app/templates/base.html index c4ded3f..9eb72e1 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,4 +1,4 @@ -{% load static sass_tags compress %} +{% load sass_tags compress %} @@ -15,8 +15,35 @@ {% block header %}