Compare commits

..

15 Commits

Author SHA1 Message Date
Markos Gogoulos
5a1e4f25ed fix: issue with create_hls 2024-11-20 15:04:27 +02:00
Markos Gogoulos
9fc7597e73 fix: GH action 2024-11-20 13:47:29 +02:00
Markos Gogoulos
9b3e0250d4 fix: GH action 2024-11-20 13:36:58 +02:00
Markos Gogoulos
1384471745 fix: flake8 2024-11-20 13:29:37 +02:00
Markos Gogoulos
29b362c8ce fix: flake8 2024-11-20 13:27:57 +02:00
Markos Gogoulos
b8ee2e9fb8 fix: pre-commit 2024-11-20 13:17:25 +02:00
Markos Gogoulos
99be0f07dd feat: edit slideshow_url results 2024-11-18 15:48:02 +02:00
Markos Gogoulos
27d1660192 feat: provide slideshow media for images (#1108) 2024-11-14 12:22:23 +02:00
Nathan Hyde
98adb22205 Fixes mediacms-io/mediacms#1046 - submition => submission (#1094) 2024-10-30 19:49:26 +02:00
yatesdr
673ddeb5bd feat: Set option for allowing only certain domains to register (#1086) 2024-10-19 14:51:20 +03:00
Markos Gogoulos
aa8a2d92dc Feat/check input (#1089)
* docs: instructions to set frames per seconds on sprites

* feat: add more validation

* remove reduntant line
2024-10-19 14:17:19 +03:00
Kaiwalya Koparkar
6bbd4c2809 feat: Added Elestio as one-click deploy option (#1055) 2024-10-08 10:44:44 +03:00
Markos Gogoulos
c4148bd504 feat: semantic release 2024-10-07 09:10:21 +03:00
Markos Gogoulos
ea8b2af26f fix: remove duplicate setting 2024-10-04 16:40:53 +03:00
Markos Gogoulos
5aa899cef0 Feat translations improvements v1 (#1076) 2024-10-04 13:39:28 +03:00
25 changed files with 174 additions and 44 deletions

View File

@@ -13,10 +13,10 @@ jobs:
uses: actions/checkout@v1 uses: actions/checkout@v1
- name: Build the Stack - name: Build the Stack
run: docker-compose -f docker-compose-dev.yaml build run: docker compose -f docker-compose-dev.yaml build
- name: Start containers - name: Start containers
run: docker-compose -f docker-compose-dev.yaml up -d run: docker compose -f docker-compose-dev.yaml up -d
- name: List containers - name: List containers
run: docker ps run: docker ps
@@ -26,10 +26,10 @@ jobs:
shell: bash shell: bash
- name: Run Django Tests - name: Run Django Tests
run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest run: docker compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest
# Run with coverage, saves report on htmlcov dir # Run with coverage, saves report on htmlcov dir
# run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest --cov --cov-report=html --cov-config=.coveragerc # run: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest --cov --cov-report=html --cov-config=.coveragerc
- name: Tear down the Stack - name: Tear down the Stack
run: docker-compose -f docker-compose-dev.yaml down run: docker compose -f docker-compose-dev.yaml down

View File

@@ -12,4 +12,8 @@ admin-shell:
build-frontend: build-frontend:
docker-compose -f docker-compose-dev.yaml exec frontend npm run dist docker-compose -f docker-compose-dev.yaml exec frontend npm run dist
cp -r frontend/dist/static/* static/ cp -r frontend/dist/static/* static/
docker-compose -f docker-compose-dev.yaml restart web docker-compose -f docker-compose-dev.yaml restart web
test:
docker compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest

View File

@@ -43,7 +43,6 @@ A demo is available at https://demo.mediacms.io
- **REST API**: Documented through Swagger - **REST API**: Documented through Swagger
- **Translation**: Most of the CMS is translated to a number of languages - **Translation**: Most of the CMS is translated to a number of languages
## Example cases ## Example cases
- **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content. - **Schools, education.** Administrators and editors keep what content will be published, students are not distracted with advertisements and irrelevant content, plus they have the ability to select either to stream or download content.
@@ -68,7 +67,12 @@ Copyright Markos Gogoulos.
We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information. We provide custom installations, development of extra functionality, migration from existing systems, integrations with legacy systems, training and support. Contact us at info@mediacms.io for more information.
### Commercial Hostings
**Elestio**
You can deploy MediaCMS on Elestio using one-click deployment. Elestio supports MediaCMS by providing revenue share so go ahead and click below to deploy and use MediaCMS.
[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/mediacms)
## Hardware considerations ## Hardware considerations

View File

@@ -9,7 +9,6 @@ DEBUG = False
# is also shown on several places as emails # is also shown on several places as emails
PORTAL_NAME = "MediaCMS" PORTAL_NAME = "MediaCMS"
PORTAL_DESCRIPTION = "" PORTAL_DESCRIPTION = ""
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Europe/London" TIME_ZONE = "Europe/London"
# who can add media # who can add media
@@ -131,6 +130,10 @@ USERS_CAN_SELF_REGISTER = True
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"] RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"]
# Comma separated list of domains: ["organization.com", "private.organization.com", "org2.com"]
# Empty list disables.
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = []
# django rest settings # django rest settings
REST_FRAMEWORK = { REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": ( "DEFAULT_AUTHENTICATION_CLASSES": (
@@ -531,3 +534,11 @@ LANGUAGES = [
] ]
LANGUAGE_CODE = 'en' # default language LANGUAGE_CODE = 'en' # default language
SPRITE_NUM_SECS = 10
# number of seconds for sprite image.
# If you plan to change this, you must also follow the instructions on admin_docs.md
# to change the equivalent value in ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and then re-build frontend
# how many images will be shown on the slideshow
SLIDESHOW_ITEMS = 30

View File

@@ -21,6 +21,7 @@
- [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file) - [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file)
- [19. Rounded corners on videos](#19-rounded-corners) - [19. Rounded corners on videos](#19-rounded-corners)
- [20. Translations](#20-translations) - [20. Translations](#20-translations)
- [21. How to change the video frames on videos](#21-fames)
## 1. Welcome ## 1. Welcome
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications. This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
@@ -354,13 +355,22 @@ ADMIN_EMAIL_LIST = ['info@mediacms.io']
### 5.13 Disallow user registrations from specific domains ### 5.13 Disallow user registrations from specific domains
set domains that are not valid for registration via this variable: Set domains that are not valid for registration via this variable:
``` ```
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [ RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [
'xxx.com', 'emaildomainwhatever.com'] 'xxx.com', 'emaildomainwhatever.com']
``` ```
Alternatively, allow only permitted domains to register. This can be useful if you're using mediacms as a private service within an organization, and want to give free registration for those in the org, but deny registration from all other domains. Setting this option bans all domains NOT in the list from registering. Default is a blank list, which is ignored. To disable, set to a blank list.
```
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = [
"private.com",
"vod.private.com",
"my.favorite.domain",
"test.private.com"]
```
### 5.14 Require a review by MediaCMS editors/managers/admins ### 5.14 Require a review by MediaCMS editors/managers/admins
set value set value
@@ -833,10 +843,21 @@ After the string is marked as translatable, add the string to `files/frontend-tr
python manage.py process_translations python manage.py process_translations
``` ```
In order to populate the string in all the languages. NO PR will be accepted if this procedure is not followed. You don't have to translate the string to all supported languages, but the command has to run and populate the existing dictionaries with the new strings for all languages. This ensures that there is no missing string to be translated in any language. In order to populate the string in all the languages. NO PR will be accepted if this procedure is not followed. You don't have to translate the string to all supported languages, but the command has to run and populate the existing dictionaries with the new strings for all languages. This ensures that there is no missing string to be translated in any language.
After this command is run, translate the string to the language you want. If the string to be translated lives in Django templates, you don't have to re-build the frontend. If the change lives in the frontend, you will have to re-build in order to see the changes. The Makefile command `make build-frontend` can help with this. After this command is run, translate the string to the language you want. If the string to be translated lives in Django templates, you don't have to re-build the frontend. If the change lives in the frontend, you will have to re-build in order to see the changes. The Makefile command `make build-frontend` can help with this.
### 20.5 Add a new language and translate ### 20.5 Add a new language and translate
To add a new language: add the language in settings.py, then add the file in `files/frontend-translations/`. Make sure you copy the initial strings by copying `files/frontend-translations/en.py` to it. To add a new language: add the language in settings.py, then add the file in `files/frontend-translations/`. Make sure you copy the initial strings by copying `files/frontend-translations/en.py` to it.
## 21. How to change the video frames on videos
By default while watching a video you can hover and see the small images named sprites that are extracted every 10 seconds of a video. You can change this number to something smaller by performing the following:
* edit ./frontend/src/static/js/components/media-viewer/VideoViewer/index.js and change `seconds: 10 ` to the value you prefer, eg 2.
* edit settings.py and set the same number for value SPRITE_NUM_SECS
* now you have to re-build the frontend: the easiest way is to run `make build-frontend`, which requires Docker
After that, newly uploaded videos will have sprites generated with the new number of seconds.

View File

@@ -1,6 +1,15 @@
from django.contrib import admin from django.contrib import admin
from .models import Category, Comment, EncodeProfile, Encoding, Language, Media, Subtitle, Tag from .models import (
Category,
Comment,
EncodeProfile,
Encoding,
Language,
Media,
Subtitle,
Tag,
)
class CommentAdmin(admin.ModelAdmin): class CommentAdmin(admin.ModelAdmin):

View File

@@ -32,7 +32,6 @@ for translation_file in files:
replacement_strings[language_code] = tr_module.replacement_strings replacement_strings[language_code] = tr_module.replacement_strings
def get_translation(language_code): def get_translation(language_code):
# get list of translations per language # get list of translations per language
if not check_language_code(language_code): if not check_language_code(language_code):

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "", "ABOUT": "",
"AUTOPLAY": "自動再生", "AUTOPLAY": "自動再生",
"About": "", "About": "",
"Add a ": "追加", "Add a ": "追加",
"COMMENT": "コメント", "COMMENT": "コメント",
"Categories": "カテゴリー", "Categories": "カテゴリー",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "정보", "ABOUT": "정보",
"AUTOPLAY": "자동 재생", "AUTOPLAY": "자동 재생",
"About": "", "About": "정보",
"Add a ": "추가", "Add a ": "추가",
"COMMENT": "댓글", "COMMENT": "댓글",
"Categories": "카테고리", "Categories": "카테고리",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "OVER", "ABOUT": "OVER",
"AUTOPLAY": "AUTOMATISCH AFSPELEN", "AUTOPLAY": "AUTOMATISCH AFSPELEN",
"About": "", "About": "Over",
"Add a ": "Voeg een ", "Add a ": "Voeg een ",
"COMMENT": "REACTIE", "COMMENT": "REACTIE",
"Categories": "Categorieën", "Categories": "Categorieën",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "SOBRE", "ABOUT": "SOBRE",
"AUTOPLAY": "REPRODUÇÃO AUTOMÁTICA", "AUTOPLAY": "REPRODUÇÃO AUTOMÁTICA",
"About": "", "About": "Sobre",
"Add a ": "Adicionar um ", "Add a ": "Adicionar um ",
"COMMENT": "COMENTÁRIO", "COMMENT": "COMENTÁRIO",
"Categories": "Categorias", "Categories": "Categorias",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "О", "ABOUT": "О",
"AUTOPLAY": "Автовоспроизведение", "AUTOPLAY": "Автовоспроизведение",
"About": "", "About": "О",
"Add a ": "Добавить ", "Add a ": "Добавить ",
"COMMENT": "КОММЕНТАРИЙ", "COMMENT": "КОММЕНТАРИЙ",
"Categories": "Категории", "Categories": "Категории",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "HAKKINDA", "ABOUT": "HAKKINDA",
"AUTOPLAY": "OTOMATİK OYNATMA", "AUTOPLAY": "OTOMATİK OYNATMA",
"About": "", "About": "Hakkında",
"Add a ": "Ekle ", "Add a ": "Ekle ",
"COMMENT": "YORUM", "COMMENT": "YORUM",
"Categories": "Kategoriler", "Categories": "Kategoriler",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "کے بارے میں", "ABOUT": "کے بارے میں",
"AUTOPLAY": "خودکار پلے", "AUTOPLAY": "خودکار پلے",
"About": "", "About": "کے بارے میں",
"Add a ": "شامل کریں", "Add a ": "شامل کریں",
"COMMENT": "تبصرہ", "COMMENT": "تبصرہ",
"Categories": "اقسام", "Categories": "اقسام",

View File

@@ -1,7 +1,7 @@
translation_strings = { translation_strings = {
"ABOUT": "关于", "ABOUT": "关于",
"AUTOPLAY": "自动播放", "AUTOPLAY": "自动播放",
"About": "", "About": "关于",
"Add a ": "添加一个", "Add a ": "添加一个",
"COMMENT": "评论", "COMMENT": "评论",
"Categories": "分类", "Categories": "分类",

View File

@@ -18,7 +18,10 @@ class Command(BaseCommand):
files = os.listdir(translations_dir) files = os.listdir(translations_dir)
files = [f for f in files if f.endswith('.py') and f not in ('__init__.py', 'en.py')] files = [f for f in files if f.endswith('.py') and f not in ('__init__.py', 'en.py')]
# Import the original English translations # Import the original English translations
from files.frontend_translations.en import replacement_strings, translation_strings from files.frontend_translations.en import (
replacement_strings,
translation_strings,
)
for file in files: for file in files:
file_path = os.path.join(translations_dir, file) file_path = os.path.join(translations_dir, file)
@@ -44,12 +47,12 @@ class Command(BaseCommand):
with open(file_path, 'w') as f: with open(file_path, 'w') as f:
f.write("translation_strings = {\n") f.write("translation_strings = {\n")
for key, value in translation_strings_wip.items(): for key, value in translation_strings_wip.items():
f.write(f' "{key}": "{value}",\n') f.write(f' "{key}": "{value}",\n') # noqa
f.write("}\n\n") f.write("}\n\n")
f.write("replacement_strings = {\n") f.write("replacement_strings = {\n")
for key, value in replacement_strings_wip.items(): for key, value in replacement_strings_wip.items():
f.write(f' "{key}": "{value}",\n') f.write(f' "{key}": "{value}",\n') # noqa
f.write("}\n") f.write("}\n")
self.stdout.write(self.style.SUCCESS(f'Processed {file}')) self.stdout.write(self.style.SUCCESS(f'Processed {file}'))

View File

@@ -780,6 +780,36 @@ class Media(models.Model):
return helpers.url_from_path(self.poster.path) return helpers.url_from_path(self.poster.path)
return None return None
@property
def slideshow_items(self):
slideshow_items = getattr(settings, "SLIDESHOW_ITEMS", 30)
if self.media_type != "image":
items = []
else:
qs = Media.objects.filter(listable=True, user=self.user, media_type="image").exclude(id=self.id).order_by('id')[:slideshow_items]
items = [
{
"poster_url": item.poster_url,
"url": item.get_absolute_url(),
"thumbnail_url": item.thumbnail_url,
"title": item.title,
"original_media_url": item.original_media_url,
}
for item in qs
]
items.insert(
0,
{
"poster_url": self.poster_url,
"url": self.get_absolute_url(),
"thumbnail_url": self.thumbnail_url,
"title": self.title,
"original_media_url": self.original_media_url,
},
)
return items
@property @property
def subtitles_info(self): def subtitles_info(self):
"""Property used on serializers """Property used on serializers

View File

@@ -145,6 +145,7 @@ class SingleMediaSerializer(serializers.ModelSerializer):
"ratings_info", "ratings_info",
"add_subtitle_url", "add_subtitle_url",
"allow_download", "allow_download",
"slideshow_items",
) )

View File

@@ -23,7 +23,17 @@ from users.models import User
from .backends import FFmpegBackend from .backends import FFmpegBackend
from .exceptions import VideoEncodingError from .exceptions import VideoEncodingError
from .helpers import calculate_seconds, create_temp_file, get_file_name, get_file_type, media_file_info, produce_ffmpeg_commands, produce_friendly_token, rm_file, run_command from .helpers import (
calculate_seconds,
create_temp_file,
get_file_name,
get_file_type,
media_file_info,
produce_ffmpeg_commands,
produce_friendly_token,
rm_file,
run_command,
)
from .methods import list_tasks, notify_users, pre_save_action from .methods import list_tasks, notify_users, pre_save_action
from .models import Category, EncodeProfile, Encoding, Media, Rating, Tag from .models import Category, EncodeProfile, Encoding, Media, Rating, Tag
@@ -374,14 +384,17 @@ def produce_sprite_from_video(friendly_token):
try: try:
tmpdir_image_files = tmpdirname + "/img%03d.jpg" tmpdir_image_files = tmpdirname + "/img%03d.jpg"
output_name = tmpdirname + "/sprites.jpg" output_name = tmpdirname + "/sprites.jpg"
cmd = "{0} -i {1} -f image2 -vf 'fps=1/10, scale=160:90' {2}&&files=$(ls {3}/img*.jpg | sort -t '-' -n -k 2 | tr '\n' ' ')&&convert $files -append {4}".format(
settings.FFMPEG_COMMAND, fps = getattr(settings, 'SPRITE_NUM_SECS', 10)
media.media_file.path, ffmpeg_cmd = [settings.FFMPEG_COMMAND, "-i", media.media_file.path, "-f", "image2", "-vf", f"fps=1/{fps}, scale=160:90", tmpdir_image_files] # noqa
tmpdir_image_files, run_command(ffmpeg_cmd)
tmpdirname, image_files = [f for f in os.listdir(tmpdirname) if f.startswith("img") and f.endswith(".jpg")]
output_name, image_files = sorted(image_files, key=lambda x: int(re.search(r'\d+', x).group()))
) image_files = [os.path.join(tmpdirname, f) for f in image_files]
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) cmd_convert = ["convert", *image_files, "-append", output_name] # image files, unpacked into the list
run_command(cmd_convert)
if os.path.exists(output_name) and get_file_type(output_name) == "image": if os.path.exists(output_name) and get_file_type(output_name) == "image":
with open(output_name, "rb") as f: with open(output_name, "rb") as f:
myfile = File(f) myfile = File(f)
@@ -421,13 +434,20 @@ def create_hls(friendly_token):
existing_output_dir = output_dir existing_output_dir = output_dir
output_dir = os.path.join(settings.HLS_DIR, p + produce_friendly_token()) output_dir = os.path.join(settings.HLS_DIR, p + produce_friendly_token())
files = " ".join([f.media_file.path for f in encodings if f.media_file]) files = " ".join([f.media_file.path for f in encodings if f.media_file])
cmd = "{0} --segment-duration=4 --output-dir={1} {2}".format(settings.MP4HLS_COMMAND, output_dir, files) cmd = [settings.MP4HLS_COMMAND, '--segment-duration=4', f'--output-dir={output_dir}', files]
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) run_command(cmd)
if existing_output_dir: if existing_output_dir:
# override content with -T ! # override content with -T !
cmd = "cp -rT {0} {1}".format(output_dir, existing_output_dir) cmd = ["cp", "-rT", output_dir, existing_output_dir]
subprocess.run(cmd, stdout=subprocess.PIPE, shell=True) run_command(cmd)
shutil.rmtree(output_dir)
try:
shutil.rmtree(output_dir)
except: # noqa
# this was breaking in some cases where it was already deleted
# because create_hls was running multiple times
pass
output_dir = existing_output_dir output_dir = existing_output_dir
pp = os.path.join(output_dir, "master.m3u8") pp = os.path.join(output_dir, "master.m3u8")
if os.path.exists(pp): if os.path.exists(pp):

View File

@@ -7,5 +7,4 @@ register = template.Library()
@register.filter @register.filter
def custom_translate(string, lang_code): def custom_translate(string, lang_code):
return translate_string(lang_code, string) return translate_string(lang_code, string)

View File

@@ -12,14 +12,24 @@ from drf_yasg import openapi as openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework import permissions, status from rest_framework import permissions, status
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from rest_framework.parsers import FileUploadParser, FormParser, JSONParser, MultiPartParser from rest_framework.parsers import (
FileUploadParser,
FormParser,
JSONParser,
MultiPartParser,
)
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
from rest_framework.views import APIView from rest_framework.views import APIView
from actions.models import USER_MEDIA_ACTIONS, MediaAction from actions.models import USER_MEDIA_ACTIONS, MediaAction
from cms.custom_pagination import FastPaginationWithoutCount from cms.custom_pagination import FastPaginationWithoutCount
from cms.permissions import IsAuthorizedToAdd, IsAuthorizedToAddComment, IsUserOrEditor, user_allowed_to_upload from cms.permissions import (
IsAuthorizedToAdd,
IsAuthorizedToAddComment,
IsUserOrEditor,
user_allowed_to_upload,
)
from users.models import User from users.models import User
from .forms import ContactForm, MediaForm, SubtitleForm from .forms import ContactForm, MediaForm, SubtitleForm
@@ -36,7 +46,16 @@ from .methods import (
show_related_media, show_related_media,
update_user_ratings, update_user_ratings,
) )
from .models import Category, Comment, EncodeProfile, Encoding, Media, Playlist, PlaylistMedia, Tag from .models import (
Category,
Comment,
EncodeProfile,
Encoding,
Media,
Playlist,
PlaylistMedia,
Tag,
)
from .serializers import ( from .serializers import (
CategorySerializer, CategorySerializer,
CommentSerializer, CommentSerializer,

View File

@@ -413,7 +413,7 @@ const CommentsListHeader = ({ commentsLength }) => {
? commentsLength + ' ' + commentsText.ucfirstPlural ? commentsLength + ' ' + commentsText.ucfirstPlural
: commentsLength + ' ' + commentsText.ucfirstSingle : commentsLength + ' ' + commentsText.ucfirstSingle
: MediaPageStore.get('media-data').enable_comments : MediaPageStore.get('media-data').enable_comments
? translateString('No') + commentsText.single + translateString('yet') ? translateString('No') + ' ' + commentsText.single + ' ' + translateString('yet')
: ''} : ''}
</h2> </h2>
) : null} ) : null}
@@ -505,7 +505,7 @@ export default function CommentsList(props) {
function onCommentSubmitFail() { function onCommentSubmitFail() {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ]. // FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout( setTimeout(
() => PageActions.addNotification(commentsText.ucfirstSingle + ' submition failed', 'commentSubmitFail'), () => PageActions.addNotification(commentsText.ucfirstSingle + ' submission failed', 'commentSubmitFail'),
100 100
); );
} }

File diff suppressed because one or more lines are too long

View File

@@ -10,6 +10,11 @@ from django.conf import settings
from . import utils from . import utils
def strip_delimiters(input_string):
delimiters = " \t\n\r'\"[]{}()<>\\|&;:*-=+"
return ''.join(char for char in input_string if char not in delimiters)
def is_valid_uuid_format(uuid_string): def is_valid_uuid_format(uuid_string):
pattern = re.compile(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', re.IGNORECASE) pattern = re.compile(r'^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$', re.IGNORECASE)
return bool(pattern.match(uuid_string)) return bool(pattern.match(uuid_string))
@@ -28,6 +33,7 @@ class BaseFineUploader(object):
self.uuid = uuid.uuid4() self.uuid = uuid.uuid4()
self.filename = os.path.basename(self.filename) self.filename = os.path.basename(self.filename)
self.filename = strip_delimiters(self.filename)
# avoid possibility of passing a fake path here # avoid possibility of passing a fake path here
self.file = data.get("qqfile") self.file = data.get("qqfile")

View File

@@ -10,6 +10,10 @@ class MyAccountAdapter(DefaultAccountAdapter):
return settings.SSL_FRONTEND_HOST + url return settings.SSL_FRONTEND_HOST + url
def clean_email(self, email): def clean_email(self, email):
if hasattr(settings, "ALLOWED_DOMAINS_FOR_USER_REGISTRATION") and settings.ALLOWED_DOMAINS_FOR_USER_REGISTRATION:
if email.split("@")[1] not in settings.ALLOWED_DOMAINS_FOR_USER_REGISTRATION:
raise ValidationError("Domain is not in the permitted list")
if email.split("@")[1] in settings.RESTRICTED_DOMAINS_FOR_USER_REGISTRATION: if email.split("@")[1] in settings.RESTRICTED_DOMAINS_FOR_USER_REGISTRATION:
raise ValidationError("Domain is restricted from registering") raise ValidationError("Domain is restricted from registering")
return email return email