mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-21 13:57:57 -05:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7136e2a11 | ||
|
|
0151e834a1 | ||
|
|
5fe4d3a9fc | ||
|
|
94c646fdb8 | ||
|
|
d665058b80 | ||
|
|
986c7d1074 | ||
|
|
1adee8c156 | ||
|
|
ffd7a52863 | ||
|
|
c5047d8df8 | ||
|
|
dcbfaca91c | ||
|
|
918df010f5 | ||
|
|
e9739bab45 | ||
|
|
e7ce9ef5c0 | ||
|
|
4829adf110 |
2
.github/workflows/python.yml
vendored
2
.github/workflows/python.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- 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: docker-compose -f docker-compose-dev.yaml exec --env TESTING=True -T web pytest --cov --cov-report=html --cov-config=.coveragerc
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
Yiannis Stergiou - ys.stergiou@gmail.com
|
||||
Markos Gogoulos - mgogoulos@gmail.com
|
||||
Swift Ugandan - swiftugandan@gmail.com
|
||||
|
||||
Please see https://github.com/mediacms-io/mediacms/graphs/contributors for complete list of contributors to this repository!
|
||||
@@ -11,6 +11,7 @@ RUN mkdir -p /home/mediacms.io/mediacms/{logs} && cd /home/mediacms.io && python
|
||||
|
||||
# Install dependencies:
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . /home/mediacms.io/mediacms
|
||||
|
||||
@@ -10,10 +10,12 @@ ENV PIP_NO_CACHE_DIR=1
|
||||
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
# Install dependencies:
|
||||
COPY requirements.txt .
|
||||
COPY requirements.txt .
|
||||
COPY requirements-dev.txt .
|
||||
RUN pip install -r requirements-dev.txt
|
||||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
RUN pip install -r requirements-dev.txt
|
||||
|
||||
COPY . /home/mediacms.io/mediacms
|
||||
WORKDIR /home/mediacms.io/mediacms
|
||||
|
||||
11
README.md
11
README.md
@@ -73,7 +73,7 @@ We provide custom installations, development of extra functionality, migration f
|
||||
|
||||
|
||||
|
||||
## Hardware dependencies
|
||||
## Hardware considerations
|
||||
|
||||
For a small to medium installation, with a few hours of video uploaded daily, and a few hundreds of active daily users viewing content, 4GB Ram / 2-4 CPUs as minimum is ok. For a larger installation with many hours of video uploaded daily, consider adding more CPUs and more Ram.
|
||||
|
||||
@@ -99,6 +99,10 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
|
||||
Visit [Configuration](docs/admins_docs.md#5-configuration) page.
|
||||
|
||||
|
||||
## Information for developers
|
||||
Check out the new section on the [Developer Experience](docs/dev_exp.md) page
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
* [Users documentation](docs/user_docs.md) page
|
||||
@@ -115,7 +119,7 @@ This software uses the following list of awesome technologies: Python, Django, D
|
||||
|
||||
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
|
||||
- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org
|
||||
- **Heritales** International Heritage Film Festival - https://stage.heritales.org
|
||||
- **American Association of Gynecologic Laparoscopists** - https://surgeryu.aagl.org/
|
||||
|
||||
|
||||
## How to contribute
|
||||
@@ -125,7 +129,8 @@ If you like the project, here's a few things you can do
|
||||
- Suggest us to others that are interested to hire us
|
||||
- Write a blog post/article about MediaCMS
|
||||
- Share on social media about the project
|
||||
- Open issues, participate on discussions, report bugs, suggest ideas
|
||||
- Open issues, participate on [discussions](https://github.com/mediacms-io/mediacms/discussions), report bugs, suggest ideas
|
||||
- [Show and tell](https://github.com/mediacms-io/mediacms/discussions/categories/show-and-tell) how you are using the project
|
||||
- Star the project
|
||||
- Add functionality, work on a PR, fix an issue!
|
||||
|
||||
|
||||
48
cms/dev_settings.py
Normal file
48
cms/dev_settings.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Development settings, used in docker-compose-dev.yaml
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'imagekit',
|
||||
'files.apps.FilesConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'actions.apps.ActionsConfig',
|
||||
'debug_toolbar',
|
||||
'mptt',
|
||||
'crispy_forms',
|
||||
'uploader.apps.UploaderConfig',
|
||||
'djcelery_email',
|
||||
'ckeditor',
|
||||
'drf_yasg',
|
||||
'corsheaders',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
]
|
||||
|
||||
DEBUG = True
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static/'),)
|
||||
STATIC_ROOT = None
|
||||
@@ -493,3 +493,13 @@ if GLOBAL_LOGIN_REQUIRED:
|
||||
DO_NOT_TRANSCODE_VIDEO = False
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
|
||||
# the following is related to local development using docker
|
||||
# and docker-compose-dev.yaml
|
||||
try:
|
||||
DEVELOPMENT_MODE = os.environ.get("DEVELOPMENT_MODE")
|
||||
if DEVELOPMENT_MODE:
|
||||
# keep a dev_settings.py file for local overrides
|
||||
from .dev_settings import * # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -28,7 +28,8 @@ else
|
||||
fi
|
||||
|
||||
# We should do this only for folders that have a different owner, since it is an expensive operation
|
||||
find /home/mediacms.io/ ! \( -user www-data -group $TARGET_GID \) -exec chown www-data:$TARGET_GID {} +
|
||||
# Also ignoring .git folder to fix this issue https://github.com/mediacms-io/mediacms/issues/934
|
||||
find /home/mediacms.io/mediacms ! \( -path "*.git*" \) -exec chown www-data:$TARGET_GID {} +
|
||||
|
||||
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ if [ X"$ENABLE_MIGRATIONS" = X"yes" ]; then
|
||||
echo "Running migrations service"
|
||||
python manage.py migrate
|
||||
EXISTING_INSTALLATION=`echo "from users.models import User; print(User.objects.exists())" |python manage.py shell`
|
||||
if [ "$EXISTING_INSTALLATION" = "True" ]; then
|
||||
if [ "$EXISTING_INSTALLATION" = "True" ]; then
|
||||
echo "Loaddata has already run"
|
||||
else
|
||||
echo "Running loaddata and creating admin user"
|
||||
@@ -67,4 +67,5 @@ fi
|
||||
if [ X"$ENABLE_CELERY_LONG" = X"yes" ] ; then
|
||||
echo "Enabling celery-long task worker"
|
||||
cp deploy/docker/supervisord/supervisord-celery_long.conf /etc/supervisor/conf.d/supervisord-celery_long.conf
|
||||
rm /var/run/mediacms/* -f # remove any stale id, so that on forced restarts of celery workers there are no stale processes that prevent new ones
|
||||
fi
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
migrations:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile-dev
|
||||
image: mediacms/mediacms-dev:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
command: "python manage.py migrate"
|
||||
environment:
|
||||
DEVELOPMENT_MODE: "True"
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
frontend:
|
||||
image: node:14
|
||||
volumes:
|
||||
@@ -18,7 +34,9 @@ services:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile-dev
|
||||
image: mediacms/mediacms-dev:latest
|
||||
command: "python manage.py runserver 0.0.0.0:80"
|
||||
environment:
|
||||
DEVELOPMENT_MODE: "True"
|
||||
ADMIN_USER: 'admin'
|
||||
ADMIN_PASSWORD: 'admin'
|
||||
ADMIN_EMAIL: 'admin@localhost'
|
||||
@@ -27,10 +45,7 @@ services:
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
- migrations
|
||||
db:
|
||||
image: postgres:15.2-alpine
|
||||
volumes:
|
||||
@@ -42,7 +57,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
@@ -54,3 +69,16 @@ services:
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
celery_worker:
|
||||
image: mediacms/mediacms-dev:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
depends_on:
|
||||
- web
|
||||
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -80,7 +80,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -100,7 +100,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
@@ -76,7 +76,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
|
||||
@@ -72,7 +72,7 @@ services:
|
||||
POSTGRES_DB: mediacms
|
||||
TZ: Europe/London
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}", "--host=db", "--dbname=$POSTGRES_DB", "--username=$POSTGRES_USER"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
- [16. Frequently Asked Questions](#16-frequently-asked-questions)
|
||||
- [17. Cookie consent code](#17-cookie-consent-code)
|
||||
- [18. Disable encoding and show only original file](#18-disable-encoding-and-show-only-original-file)
|
||||
- [19. Rounded corners on videos](#19-rounded-corners)
|
||||
|
||||
## 1. Welcome
|
||||
This page is created for MediaCMS administrators that are responsible for setting up the software, maintaining it and making modifications.
|
||||
@@ -778,4 +779,15 @@ When videos are uploaded, they are getting encoded to multiple resolutions, a pr
|
||||
DO_NOT_TRANSCODE_VIDEO = True
|
||||
```
|
||||
|
||||
This will disable the transcoding process and only the original file will be shown. Note that this will also disable the sprites file creation, so you will not have the preview thumbnails on the video player.
|
||||
This will disable the transcoding process and only the original file will be shown. Note that this will also disable the sprites file creation, so you will not have the preview thumbnails on the video player.
|
||||
|
||||
## 19. Rounded corners on videos
|
||||
|
||||
By default the video player and media items are now having rounded corners, on larger screens (not in mobile). If you don't like this change, remove the `border-radius` added on the following files:
|
||||
|
||||
```
|
||||
frontend/src/static/css/_extra.css
|
||||
frontend/src/static/js/components/list-item/Item.scss
|
||||
frontend/src/static/js/components/media-page/MediaPage.scss
|
||||
```
|
||||
you now have to re-run the frontend build in order to see the changes (check docs/dev_exp.md)
|
||||
|
||||
60
docs/dev_exp.md
Normal file
60
docs/dev_exp.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Developer Experience
|
||||
There is ongoing effort to provide a better developer experience and document it.
|
||||
|
||||
## How to develop locally with Docker
|
||||
First install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
Then run `docker-compose -f docker-compose-dev.yaml up`
|
||||
|
||||
```
|
||||
user@user:~/mediacms$ docker-compose -f docker-compose-dev.yaml up
|
||||
```
|
||||
|
||||
In a few minutes the app will be available at http://localhost . Login via admin/admin
|
||||
|
||||
### What does docker-compose-dev.yaml do?
|
||||
It build the two images used for backend and frontend.
|
||||
|
||||
* Backend: `mediacms/mediacms-dev:latest`
|
||||
* Frontend: `frontend`
|
||||
|
||||
and will start all services required for MediaCMS, as Celery/Redis for asynchronous tasks, PostgreSQL database, Django and React
|
||||
|
||||
For Django, the changes from the image produced by docker-compose.yaml are these:
|
||||
|
||||
* Django runs in debug mode, with `python manage.py runserver`
|
||||
* uwsgi and nginx are not run
|
||||
* Django runs in Debug mode, with Debug Toolbar
|
||||
* Static files (js/css) are loaded from static/ folder
|
||||
* corsheaders is installed and configured to allow all origins
|
||||
|
||||
For React, it will run `npm start` in the frontend folder, which will start the development server.
|
||||
Check it on http://localhost:8088/
|
||||
|
||||
### How to develop in Django
|
||||
Django starts at http://localhost and is reloading automatically. Making any change to the python code should refresh Django.
|
||||
|
||||
### How to develop in React
|
||||
React is started on http://localhost:8088/ , code is located in frontend/ , so making changes there should have instant effect on the page. Keep in mind that React is loading data from Django, and that it has to be built so that Django can serve it.
|
||||
|
||||
### Making changes to the frontend
|
||||
|
||||
The way React is added is more complicated than the usual SPA project and this is because React is used as a library loaded by Django Templates, so it is not a standalone project and is not handling routes etc.
|
||||
|
||||
The two directories to consider are:
|
||||
* frontend/src , for the React files
|
||||
* templates/, for the Django templates.
|
||||
|
||||
Django is using a highly intuitive hierarchical templating system (https://docs.djangoproject.com/en/4.2/ref/templates/), where the base template is templates/root.html and all other templates are extending it.
|
||||
|
||||
React is called through the Django templates, eg templates/cms/media.html is loading js/media.js
|
||||
|
||||
In order to make changes to React code, edit code on frontend/src and check it's effect on http://localhost:8088/ . Once ready, build it and copy it to the Django static folder, so that it is served by Django.
|
||||
|
||||
### Development workflow with the frontend
|
||||
1. Edit frontend/src/ files
|
||||
2. Check changes on http://localhost:8088/
|
||||
3. Build frontend with `docker-compose -f docker-compose-dev.yaml exec frontend npm run dist`
|
||||
4. Copy static files to Django static folder with`cp -r frontend/dist/static/* static/`
|
||||
5. Restart Django - `docker-compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
|
||||
6. Commit the changes
|
||||
@@ -40,6 +40,12 @@ class MediaAdmin(admin.ModelAdmin):
|
||||
def get_comments_count(self, obj):
|
||||
return obj.comments.count()
|
||||
|
||||
@admin.action(description="Generate missing encoding(s)", permissions=["change"])
|
||||
def generate_missing_encodings(modeladmin, request, queryset):
|
||||
for m in queryset:
|
||||
m.encode(force=False)
|
||||
|
||||
actions = [generate_missing_encodings]
|
||||
get_comments_count.short_description = "Comments count"
|
||||
|
||||
|
||||
@@ -74,7 +80,18 @@ class SubtitleAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class EncodingAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
list_display = ["get_title", "chunk", "profile", "progress", "status", "has_file"]
|
||||
list_filter = ["chunk", "profile", "status"]
|
||||
|
||||
def get_title(self, obj):
|
||||
return str(obj)
|
||||
|
||||
get_title.short_description = "Encoding"
|
||||
|
||||
def has_file(self, obj):
|
||||
return obj.media_encoding_url is not None
|
||||
|
||||
has_file.short_description = "Has file"
|
||||
|
||||
|
||||
admin.site.register(EncodeProfile, EncodeProfileAdmin)
|
||||
|
||||
@@ -644,7 +644,11 @@ def save_user_action(user_or_session, friendly_token=None, action="watch", extra
|
||||
|
||||
if action == "watch":
|
||||
media.views += 1
|
||||
media.save(update_fields=["views"])
|
||||
Media.objects.filter(friendly_token=friendly_token).update(views=media.views)
|
||||
|
||||
# update field without calling save, to avoid post_save signals being triggered
|
||||
# same in other actions
|
||||
|
||||
elif action == "report":
|
||||
media.reported_times += 1
|
||||
|
||||
@@ -659,10 +663,10 @@ def save_user_action(user_or_session, friendly_token=None, action="watch", extra
|
||||
)
|
||||
elif action == "like":
|
||||
media.likes += 1
|
||||
media.save(update_fields=["likes"])
|
||||
Media.objects.filter(friendly_token=friendly_token).update(likes=media.likes)
|
||||
elif action == "dislike":
|
||||
media.dislikes += 1
|
||||
media.save(update_fields=["dislikes"])
|
||||
Media.objects.filter(friendly_token=friendly_token).update(dislikes=media.dislikes)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -598,14 +598,15 @@ class MediaDetail(APIView):
|
||||
media = self.get_object(friendly_token)
|
||||
if isinstance(media, Response):
|
||||
return media
|
||||
|
||||
serializer = MediaSerializer(media, data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
if request.data.get('media_file'):
|
||||
media_file = request.data["media_file"]
|
||||
serializer.save(user=request.user, media_file=media_file)
|
||||
else:
|
||||
serializer.save(user=request.user)
|
||||
serializer.save(user=request.user)
|
||||
# no need to update the media file itself, only the metadata
|
||||
# if request.data.get('media_file'):
|
||||
# media_file = request.data["media_file"]
|
||||
# serializer.save(user=request.user, media_file=media_file)
|
||||
# else:
|
||||
# serializer.save(user=request.user)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
124
frontend/packages/player/dist/mediacms-player.css
vendored
124
frontend/packages/player/dist/mediacms-player.css
vendored
@@ -61,8 +61,8 @@
|
||||
/* SPACES */
|
||||
/* ANIMATIONS */
|
||||
/* FUNCTIONS */
|
||||
/*@function calc_($expression) {
|
||||
@return $expression;
|
||||
/*@function calc_($expression) {
|
||||
@return $expression;
|
||||
}*/
|
||||
/* ANIMATION KEYFRAMES */
|
||||
@keyframes onHoverFullscreenToggle {
|
||||
@@ -232,11 +232,11 @@
|
||||
outline-color: rgba(0, 0, 0, 0);
|
||||
outline-color: transparent;
|
||||
/* Doesn't work properly in Safari browser.*/
|
||||
/*&.vjs-loading-video {
|
||||
video {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
/*&.vjs-loading-video {
|
||||
video {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}*/ }
|
||||
.video-js.vjs-mediacms video {
|
||||
width: 100%;
|
||||
@@ -321,11 +321,11 @@
|
||||
display: none;
|
||||
font-size: 0.8125em;
|
||||
z-index: +1;
|
||||
/*display:block;
|
||||
opacity: 0;
|
||||
height:0;
|
||||
visibility: hidden;
|
||||
@include transition( opacity 0.25s cubic-bezier(0.0,0.0,0.2,1) );
|
||||
/*display:block;
|
||||
opacity: 0;
|
||||
height:0;
|
||||
visibility: hidden;
|
||||
@include transition( opacity 0.25s cubic-bezier(0.0,0.0,0.2,1) );
|
||||
will-change:height;*/ }
|
||||
.video-js.vjs-mediacms .vjs-settings-panel .vjs-setting-panel-title > [role='button'] {
|
||||
position: relative; }
|
||||
@@ -337,8 +337,8 @@
|
||||
outline: 0; }
|
||||
.video-js.vjs-mediacms .vjs-settings-panel.vjs-visible-panel {
|
||||
display: block;
|
||||
/*opacity: 1;
|
||||
visibility: visible;
|
||||
/*opacity: 1;
|
||||
visibility: visible;
|
||||
height:auto;*/ }
|
||||
.video-js.vjs-mediacms .vjs-settings-panel .vjs-settings-panel-inner {
|
||||
display: block;
|
||||
@@ -679,7 +679,7 @@
|
||||
.video-js.vjs-mediacms .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal {
|
||||
transition: width 0.2s linear;
|
||||
transition-delay: 0.15s;
|
||||
/* transition: width 0s linear;
|
||||
/* transition: width 0s linear;
|
||||
transition-delay: 0s; */ }
|
||||
.video-js.vjs-mediacms .vjs-actions-anim {
|
||||
-webkit-user-select: none;
|
||||
@@ -986,31 +986,31 @@
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
/*@-moz-document url-prefix() {
|
||||
|
||||
position: relative;
|
||||
padding-right: $font-size * 1.1;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
background-color: $bg-color;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
float: right;
|
||||
text-align:right;
|
||||
content: '\2026';
|
||||
width: $font-size * 1.1;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: +1;
|
||||
background: $bg-color;
|
||||
}
|
||||
/*@-moz-document url-prefix() {
|
||||
|
||||
position: relative;
|
||||
padding-right: $font-size * 1.1;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
background-color: $bg-color;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
float: right;
|
||||
text-align:right;
|
||||
content: '\2026';
|
||||
width: $font-size * 1.1;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: +1;
|
||||
background: $bg-color;
|
||||
}
|
||||
}*/
|
||||
color: #eee; }
|
||||
.video-js.vjs-mediacms .vjs-corner-layer .title-link:hover {
|
||||
@@ -1057,28 +1057,28 @@
|
||||
.video-js.vjs-mediacms:hover .vjs-big-play-button {
|
||||
background-color: #009933; }
|
||||
|
||||
/* @-webkit-keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
/* @-webkit-keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
} */
|
||||
.video-js.vjs-mediacms .vjs-progress-control .vjs-mouse-display .vjs-time-tooltip,
|
||||
.video-js.vjs-mediacms .vjs-preview-thumb .vjs-preview-thumb-time-display {
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
/* SPACES */
|
||||
/* ANIMATIONS */
|
||||
/* FUNCTIONS */
|
||||
/*@function calc_($expression) {
|
||||
@return $expression;
|
||||
/*@function calc_($expression) {
|
||||
@return $expression;
|
||||
}*/
|
||||
/* ANIMATION KEYFRAMES */
|
||||
@keyframes onHoverFullscreenToggle {
|
||||
@@ -232,11 +232,11 @@
|
||||
outline-color: rgba(0, 0, 0, 0);
|
||||
outline-color: transparent;
|
||||
/* Doesn't work properly in Safari browser.*/
|
||||
/*&.vjs-loading-video {
|
||||
video {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
/*&.vjs-loading-video {
|
||||
video {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}*/ }
|
||||
.video-js.vjs-mediacms video {
|
||||
width: 100%;
|
||||
@@ -321,11 +321,11 @@
|
||||
display: none;
|
||||
font-size: 0.8125em;
|
||||
z-index: +1;
|
||||
/*display:block;
|
||||
opacity: 0;
|
||||
height:0;
|
||||
visibility: hidden;
|
||||
@include transition( opacity 0.25s cubic-bezier(0.0,0.0,0.2,1) );
|
||||
/*display:block;
|
||||
opacity: 0;
|
||||
height:0;
|
||||
visibility: hidden;
|
||||
@include transition( opacity 0.25s cubic-bezier(0.0,0.0,0.2,1) );
|
||||
will-change:height;*/ }
|
||||
.video-js.vjs-mediacms .vjs-settings-panel .vjs-setting-panel-title > [role='button'] {
|
||||
position: relative; }
|
||||
@@ -337,8 +337,8 @@
|
||||
outline: 0; }
|
||||
.video-js.vjs-mediacms .vjs-settings-panel.vjs-visible-panel {
|
||||
display: block;
|
||||
/*opacity: 1;
|
||||
visibility: visible;
|
||||
/*opacity: 1;
|
||||
visibility: visible;
|
||||
height:auto;*/ }
|
||||
.video-js.vjs-mediacms .vjs-settings-panel .vjs-settings-panel-inner {
|
||||
display: block;
|
||||
@@ -679,7 +679,7 @@
|
||||
.video-js.vjs-mediacms .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal {
|
||||
transition: width 0.2s linear;
|
||||
transition-delay: 0.15s;
|
||||
/* transition: width 0s linear;
|
||||
/* transition: width 0s linear;
|
||||
transition-delay: 0s; */ }
|
||||
.video-js.vjs-mediacms .vjs-actions-anim {
|
||||
-webkit-user-select: none;
|
||||
@@ -986,31 +986,31 @@
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
/*@-moz-document url-prefix() {
|
||||
|
||||
position: relative;
|
||||
padding-right: $font-size * 1.1;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
background-color: $bg-color;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
float: right;
|
||||
text-align:right;
|
||||
content: '\2026';
|
||||
width: $font-size * 1.1;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: +1;
|
||||
background: $bg-color;
|
||||
}
|
||||
/*@-moz-document url-prefix() {
|
||||
|
||||
position: relative;
|
||||
padding-right: $font-size * 1.1;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
background-color: $bg-color;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
float: right;
|
||||
text-align:right;
|
||||
content: '\2026';
|
||||
width: $font-size * 1.1;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: +1;
|
||||
background: $bg-color;
|
||||
}
|
||||
}*/
|
||||
color: #eee; }
|
||||
.video-js.vjs-mediacms .vjs-corner-layer .title-link:hover {
|
||||
@@ -1057,28 +1057,28 @@
|
||||
.video-js.vjs-mediacms:hover .vjs-big-play-button {
|
||||
background-color: #009933; }
|
||||
|
||||
/* @-webkit-keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
/* @-webkit-keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vjs-poster-reveal {
|
||||
0%{
|
||||
opacity:0;
|
||||
}
|
||||
40%{
|
||||
opacity:0;
|
||||
}
|
||||
100%{
|
||||
opacity:1;
|
||||
}
|
||||
} */
|
||||
.video-js.vjs-mediacms .vjs-progress-control .vjs-mouse-display .vjs-time-tooltip,
|
||||
.video-js.vjs-mediacms .vjs-preview-thumb .vjs-preview-thumb-time-display {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
body {
|
||||
/* THIS is not a good way to handle this, and a proper refactoring needs to be performed
|
||||
This allows bigger images of media on listings for large screens
|
||||
It would be great to adapt to other sizes, but this requires a good refaftoring
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 2200px) {
|
||||
--default-item-width: 342px !important;
|
||||
--default-max-item-width: 342px !important;
|
||||
--default-item-margin-right-width: 17px !important;
|
||||
--default-item-margin-bottom-width: 27px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
|
||||
.item-thumb,
|
||||
a.item-thumb {
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
display: block;
|
||||
height: auto;
|
||||
|
||||
@@ -22,7 +22,7 @@ function downloadOptions(mediaData, allowDownload) {
|
||||
if (Object.keys(encodingsInfo[k]).length) {
|
||||
for (g in encodingsInfo[k]) {
|
||||
if (encodingsInfo[k].hasOwnProperty(g)) {
|
||||
if ('success' === encodingsInfo[k][g].status && 100 === encodingsInfo[k][g].progress) {
|
||||
if ('success' === encodingsInfo[k][g].status && 100 === encodingsInfo[k][g].progress && null !== encodingsInfo[k][g].url) {
|
||||
options[encodingsInfo[k][g].title] = {
|
||||
text: k + ' - ' + g.toUpperCase() + ' (' + encodingsInfo[k][g].size + ')',
|
||||
link: formatInnerLink(encodingsInfo[k][g].url, site.url),
|
||||
|
||||
@@ -19,7 +19,7 @@ function downloadOptionsList() {
|
||||
if (Object.keys(encodings_info[k]).length) {
|
||||
for (g in encodings_info[k]) {
|
||||
if (encodings_info[k].hasOwnProperty(g)) {
|
||||
if ('success' === encodings_info[k][g].status && 100 === encodings_info[k][g].progress) {
|
||||
if ('success' === encodings_info[k][g].status && 100 === encodings_info[k][g].progress && null !== encodings_info[k][g].url) {
|
||||
optionsList[encodings_info[k][g].title] = {
|
||||
text: k + ' - ' + g.toUpperCase() + ' (' + encodings_info[k][g].size + ')',
|
||||
link: formatInnerLink(encodings_info[k][g].url, SiteContext._currentValue.url),
|
||||
|
||||
@@ -530,7 +530,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.viewer-container .player-container {
|
||||
@media screen and (min-width: 480px) {
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.viewer-container .player-container.audio-player-container {
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
padding-top: 0.75 * 56.25%;
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ pytest-cov
|
||||
pytest-django
|
||||
pytest-factoryboy
|
||||
Faker
|
||||
django-cors-headers
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
|
||||
body {
|
||||
/* THIS is not a good way to handle this, and a proper refactoring needs to be performed
|
||||
This allows bigger images of media on listings for large screens
|
||||
It would be great to adapt to other sizes, but this requires a good refaftoring
|
||||
*/
|
||||
|
||||
@media screen and (min-width: 2200px) {
|
||||
--default-item-width: 342px !important;
|
||||
--default-max-item-width: 342px !important;
|
||||
--default-item-margin-right-width: 17px !important;
|
||||
--default-item-margin-bottom-width: 27px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from io import StringIO
|
||||
from os.path import join
|
||||
|
||||
@@ -7,12 +10,26 @@ from django.conf import settings
|
||||
from . import utils
|
||||
|
||||
|
||||
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)
|
||||
return bool(pattern.match(uuid_string))
|
||||
|
||||
|
||||
class BaseFineUploader(object):
|
||||
def __init__(self, data, *args, **kwargs):
|
||||
self.data = data
|
||||
self.total_filesize = data.get("qqtotalfilesize")
|
||||
self.filename = data.get("qqfilename")
|
||||
self.uuid = data.get("qquuid")
|
||||
|
||||
if not is_valid_uuid_format(self.uuid):
|
||||
# something nasty client side could be happening here
|
||||
# generate new uuid to ensure this is uuid
|
||||
# not sure if this will work with the chunked uploads though
|
||||
self.uuid = uuid.uuid4()
|
||||
|
||||
self.filename = os.path.basename(self.filename)
|
||||
# avoid possibility of passing a fake path here
|
||||
|
||||
self.file = data.get("qqfile")
|
||||
self.storage_class = settings.FILE_STORAGE
|
||||
self.real_path = None
|
||||
@@ -50,7 +67,11 @@ class ChunkedFineUploader(BaseFineUploader):
|
||||
self.total_parts = data.get("qqtotalparts")
|
||||
if not isinstance(self.total_parts, int):
|
||||
self.total_parts = 1
|
||||
self.part_index = data.get("qqpartindex")
|
||||
qqpartindex = data.get("qqpartindex")
|
||||
if not isinstance(qqpartindex, int):
|
||||
# something nasty client side could be happening here
|
||||
qqpartindex = 0
|
||||
self.part_index = qqpartindex
|
||||
|
||||
@property
|
||||
def chunks_path(self):
|
||||
@@ -75,6 +96,7 @@ class ChunkedFineUploader(BaseFineUploader):
|
||||
def combine_chunks(self):
|
||||
# implement the same behaviour.
|
||||
self.real_path = self.storage.save(self._full_file_path, StringIO())
|
||||
|
||||
with self.storage.open(self.real_path, "wb") as final_file:
|
||||
for i in range(self.total_parts):
|
||||
part = join(self.chunks_path, str(i))
|
||||
|
||||
Reference in New Issue
Block a user