diff --git a/.github/workflows/check_vulnerabilities.yaml b/.github/workflows/check_vulnerabilities.yaml index c4f0c1267..6cd8116da 100644 --- a/.github/workflows/check_vulnerabilities.yaml +++ b/.github/workflows/check_vulnerabilities.yaml @@ -22,3 +22,5 @@ jobs: inputs: $PRODUCTION $DEVELOP ignore-vulns: | GHSA-gw84-84pc-xp82 # requires DRF 3.15 and Django 4 + GHSA-rrqc-c2jx-6jgv # requires Django 4 + GHSA-jj5c-hhrg-vv5h # there is no update available for xhtml2pdf so far diff --git a/apps/articles/migrations/0015_auto_20241022_2318.py b/apps/articles/migrations/0015_auto_20241022_2318.py new file mode 100644 index 000000000..69d4cc46b --- /dev/null +++ b/apps/articles/migrations/0015_auto_20241022_2318.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.25 on 2024-10-22 20:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('articles', '0014_project_description_caption'), + ] + + operations = [ + migrations.AlterField( + model_name='blogitemcontent', + name='content_type', + field=models.ForeignKey(limit_choices_to={'app_label': 'content_pages', 'model__in': ('contentunitrichtext', 'embedcode', 'eventsblock', 'imagesblock', 'link', 'personsblock', 'playsblock', 'videosblock')}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='Тип объекта'), + ), + migrations.AlterField( + model_name='newsitemcontent', + name='content_type', + field=models.ForeignKey(limit_choices_to={'app_label': 'content_pages', 'model__in': ('contentunitrichtext', 'embedcode', 'eventsblock', 'imagesblock', 'link', 'personsblock', 'playsblock', 'videosblock')}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='Тип объекта'), + ), + migrations.AlterField( + model_name='projectcontent', + name='content_type', + field=models.ForeignKey(limit_choices_to={'app_label': 'content_pages', 'model__in': ('contentunitrichtext', 'embedcode', 'eventsblock', 'imagesblock', 'link', 'personsblock', 'playsblock', 'videosblock')}, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='Тип объекта'), + ), + ] diff --git a/apps/content_pages/admin/__init__.py b/apps/content_pages/admin/__init__.py index c90a943bb..3f940e8b7 100644 --- a/apps/content_pages/admin/__init__.py +++ b/apps/content_pages/admin/__init__.py @@ -10,5 +10,5 @@ PlaysBlockAdmin, VideosBlockAdmin, ) -from apps.content_pages.admin.content_items import Link +from apps.content_pages.admin.content_items import EmbedCode, Link from apps.content_pages.admin.contents import BaseContentInline, BaseContentPageAdmin diff --git a/apps/content_pages/admin/content_items.py b/apps/content_pages/admin/content_items.py index a00f37329..118f7491a 100644 --- a/apps/content_pages/admin/content_items.py +++ b/apps/content_pages/admin/content_items.py @@ -1,6 +1,6 @@ from django.contrib import admin -from apps.content_pages.models import ContentUnitRichText, Link +from apps.content_pages.models import ContentUnitRichText, EmbedCode, Link from apps.core.mixins import HideOnNavPanelAdminModelMixin @@ -10,3 +10,4 @@ class ModelAdminToHide(HideOnNavPanelAdminModelMixin, admin.ModelAdmin): admin.site.register(ContentUnitRichText, ModelAdminToHide) admin.site.register(Link, ModelAdminToHide) +admin.site.register(EmbedCode, ModelAdminToHide) diff --git a/apps/content_pages/migrations/0009_embedcode.py b/apps/content_pages/migrations/0009_embedcode.py new file mode 100644 index 000000000..73f7aad9d --- /dev/null +++ b/apps/content_pages/migrations/0009_embedcode.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.25 on 2024-10-22 20:02 + +import apps.content_pages.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('content_pages', '0008_alter_extendedperson_options'), + ] + + operations = [ + migrations.CreateModel( + name='EmbedCode', + 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)), + ('title', models.CharField(max_length=250, verbose_name='Заголовок')), + ('code', models.TextField(max_length=500, validators=[apps.content_pages.validators.iframe_validator], verbose_name='Тег iframe для встраиваемого содержимого')), + ], + options={ + 'verbose_name': 'Встраиваемое содержимое', + 'verbose_name_plural': 'Встраиваемое содержимое', + }, + ), + ] diff --git a/apps/content_pages/models/__init__.py b/apps/content_pages/models/__init__.py index c4bc2b2a4..584b8b41e 100644 --- a/apps/content_pages/models/__init__.py +++ b/apps/content_pages/models/__init__.py @@ -1,4 +1,4 @@ -from apps.content_pages.models.content_items import AbstractItemWithTitle, ContentUnitRichText, Link +from apps.content_pages.models.content_items import AbstractItemWithTitle, ContentUnitRichText, EmbedCode, Link from apps.content_pages.models.contents import AbstractContent, AbstractContentPage # Prevent isort to rearrange imports and prevent circular imports. diff --git a/apps/content_pages/models/content_items.py b/apps/content_pages/models/content_items.py index 6cf63f952..d687dc053 100644 --- a/apps/content_pages/models/content_items.py +++ b/apps/content_pages/models/content_items.py @@ -2,6 +2,7 @@ from django.db import models from django.utils.html import strip_tags +from apps.content_pages.validators import iframe_validator from apps.core.models import BaseModel @@ -53,3 +54,15 @@ class Link(AbstractItemWithTitle): class Meta: verbose_name = "Ссылка с описанием" verbose_name_plural = "Ссылки с описанием" + + +class EmbedCode(AbstractItemWithTitle): + """Embeddable iframe link.""" + + code = models.TextField( + max_length=500, verbose_name="Тег iframe для встраиваемого содержимого", validators=(iframe_validator,) + ) + + class Meta: + verbose_name = "Встраиваемое содержимое" + verbose_name_plural = "Встраиваемое содержимое" diff --git a/apps/content_pages/models/contents.py b/apps/content_pages/models/contents.py index ef3372b77..c9722e8a3 100644 --- a/apps/content_pages/models/contents.py +++ b/apps/content_pages/models/contents.py @@ -104,6 +104,7 @@ class AbstractContent(models.Model): "app_label": "content_pages", "model__in": ( "contentunitrichtext", + "embedcode", "eventsblock", "imagesblock", "link", diff --git a/apps/content_pages/serializers/__init__.py b/apps/content_pages/serializers/__init__.py index 877e6687b..f7c049288 100644 --- a/apps/content_pages/serializers/__init__.py +++ b/apps/content_pages/serializers/__init__.py @@ -7,6 +7,7 @@ ) from apps.content_pages.serializers.content_items import ( ContentUnitRichTextSerializer, + EmbdedCodeSerializer, ExtendedPersonSerializer, LinkSerializer, OrderedImageSerializer, diff --git a/apps/content_pages/serializers/content_items.py b/apps/content_pages/serializers/content_items.py index ce3d6dcd7..ed3f22937 100644 --- a/apps/content_pages/serializers/content_items.py +++ b/apps/content_pages/serializers/content_items.py @@ -1,6 +1,6 @@ from rest_framework import serializers -from apps.content_pages.models import Link, OrderedImage, OrderedVideo +from apps.content_pages.models import EmbedCode, Link, OrderedImage, OrderedVideo from apps.core.mixins import GetDomainMixin from apps.core.serializers import PersonRoleSerializer from apps.library.serializers import AuthorForPlaySerializer as LibraryPlayAuthorSerializer @@ -66,6 +66,15 @@ class Meta: ) +class EmbdedCodeSerializer(serializers.ModelSerializer): + class Meta: + model = EmbedCode + fields = ( + "title", + "code", + ) + + class OrderedImageSerializer(serializers.ModelSerializer): class Meta: model = OrderedImage diff --git a/apps/content_pages/serializers/contents.py b/apps/content_pages/serializers/contents.py index a11415a24..2c88b143e 100644 --- a/apps/content_pages/serializers/contents.py +++ b/apps/content_pages/serializers/contents.py @@ -3,6 +3,7 @@ from apps.content_pages.models import ( ContentUnitRichText, + EmbedCode, EventsBlock, ImagesBlock, Link, @@ -12,6 +13,7 @@ ) from apps.content_pages.serializers import ( ContentUnitRichTextSerializer, + EmbdedCodeSerializer, EventsBlockSerializer, ImagesBlockSerializer, LinkSerializer, @@ -22,6 +24,7 @@ CONTENT_OBJECT_SERIALIZER_PAIRS = { ContentUnitRichText: ContentUnitRichTextSerializer, + EmbedCode: EmbdedCodeSerializer, EventsBlock: EventsBlockSerializer, ImagesBlock: ImagesBlockSerializer, Link: LinkSerializer, diff --git a/apps/content_pages/validators.py b/apps/content_pages/validators.py new file mode 100644 index 000000000..384f27a88 --- /dev/null +++ b/apps/content_pages/validators.py @@ -0,0 +1,7 @@ +from django.core.exceptions import ValidationError + + +def iframe_validator(code: str): + code = code.strip() + if not (code.startswith("")