Compare commits

...

354 Commits

Author SHA1 Message Date
Markos Gogoulos
48537515cb changes 2026-01-30 16:14:55 +02:00
Markos Gogoulos
e6db138d11 xif 2026-01-30 15:17:22 +02:00
Markos Gogoulos
2f2d32f0db fix 2026-01-30 13:36:41 +02:00
Markos Gogoulos
f4d3439246 fix 2026-01-30 13:31:47 +02:00
Markos Gogoulos
7fe9891942 wtv 2026-01-30 13:30:32 +02:00
Markos Gogoulos
9eb8a1ad62 plug 2026-01-30 13:04:22 +02:00
Markos Gogoulos
23ee0dc7cc new 2026-01-30 13:01:21 +02:00
Markos Gogoulos
e5be39f392 feat: make S3 bucket for Terraform unique 2026-01-30 11:58:57 +02:00
Markos Gogoulos
f0c084fa53 wtv 2026-01-30 11:55:15 +02:00
Markos Gogoulos
571bfcc4ce wtv 2026-01-29 16:36:57 +02:00
Markos Gogoulos
c04380af47 w 2026-01-29 16:09:01 +02:00
Markos Gogoulos
97741f780e wtv 2026-01-29 16:03:35 +02:00
Markos Gogoulos
78cce0eb10 wtv 2026-01-29 16:01:34 +02:00
Markos Gogoulos
472b3029c4 wtv 2026-01-29 15:58:07 +02:00
Markos Gogoulos
343f1e7009 wtv 2026-01-29 15:49:11 +02:00
Markos Gogoulos
8c78b67b0c wtv 2026-01-29 15:40:33 +02:00
Markos Gogoulos
29fc7fb861 wtv 2026-01-29 15:37:41 +02:00
Markos Gogoulos
b03a33d93e wtv 2026-01-29 15:31:44 +02:00
Markos Gogoulos
64472be406 wtv 2026-01-29 15:27:49 +02:00
Markos Gogoulos
cc0f4d4645 new 2026-01-29 15:18:15 +02:00
Markos Gogoulos
095e4d2cb4 latest 2026-01-29 14:58:44 +02:00
Markos Gogoulos
5c8978453e a 2026-01-29 14:52:48 +02:00
Markos Gogoulos
83189076e4 a 2026-01-29 14:42:44 +02:00
Markos Gogoulos
ca6dbf3740 rev 2026-01-29 12:57:40 +02:00
Markos Gogoulos
8646bd70dc wtv 2026-01-29 12:49:45 +02:00
Markos Gogoulos
1f493c8e15 wtv 2026-01-29 12:49:37 +02:00
Markos Gogoulos
e11cb7ea6e wtv 2026-01-29 12:45:04 +02:00
Markos Gogoulos
3131e76ef7 this 2026-01-29 12:30:57 +02:00
Markos Gogoulos
809cdccc42 remove 2026-01-29 12:30:06 +02:00
Markos Gogoulos
ed36240f45 wtv 2026-01-28 22:46:27 +02:00
Markos Gogoulos
77bafff6f6 wtv 2026-01-28 22:44:22 +02:00
Markos Gogoulos
f6252f4f77 wtv 2026-01-28 22:40:48 +02:00
Markos Gogoulos
764580287f tv 2026-01-28 22:36:47 +02:00
Markos Gogoulos
ce6c9a0a3c tv 2026-01-28 22:35:02 +02:00
Markos Gogoulos
1ced023a07 tv 2026-01-28 22:30:13 +02:00
Markos Gogoulos
981fec296c tv 2026-01-28 22:27:19 +02:00
Markos Gogoulos
40cd7916e7 this 2026-01-28 22:25:33 +02:00
Yiannis Christodoulou
bcef59c3a9 Update select_media.html 2026-01-28 17:19:15 +02:00
Markos Gogoulos
e93c8225c4 d 2026-01-25 11:37:26 +02:00
Markos Gogoulos
5c3c33ca84 this 2026-01-25 10:26:58 +02:00
Markos Gogoulos
7a954e7a3d this 2026-01-25 10:18:00 +02:00
Markos Gogoulos
8610df0c2b this 2026-01-16 13:45:06 +02:00
Markos Gogoulos
8ab9030d14 this 2026-01-16 13:44:46 +02:00
Markos Gogoulos
15c8dec041 you 2026-01-16 13:44:04 +02:00
Markos Gogoulos
9af4686bd4 static 2026-01-10 15:59:44 +02:00
Markos Gogoulos
bcc8a0858c droplist for actions 2026-01-10 15:58:16 +02:00
Markos Gogoulos
549b672d48 add friendly_token in indexing 2026-01-10 15:30:03 +02:00
Markos Gogoulos
abe950f1da w 2026-01-09 13:42:36 +02:00
Markos Gogoulos
5fecda02d6 w 2026-01-09 13:40:20 +02:00
Markos Gogoulos
3c6f8c102c same window 2026-01-09 13:37:18 +02:00
Markos Gogoulos
2d28520cd4 Merge branch 'main' into feat-lti-integration 2026-01-09 13:29:52 +02:00
Markos Gogoulos
4bd56da2d8 push 2026-01-09 13:29:18 +02:00
Markos Gogoulos
fdfa857794 fix 2026-01-08 17:44:44 +02:00
Markos Gogoulos
2c1f27c0be remove 2026-01-08 17:30:33 +02:00
Markos Gogoulos
2f0bbd2533 gunicorn 2026-01-08 17:29:41 +02:00
Yiannis Stergiou
1c15880ae3 feat: frontend unit tests 2026-01-07 19:47:54 +02:00
Markos Gogoulos
54336f6c31 fix 2026-01-06 17:19:47 +02:00
Markos Gogoulos
37e21f7ebf this 2025-12-30 19:57:27 +02:00
Markos Gogoulos
3deee80dd0 fix 2025-12-30 19:53:30 +02:00
Markos Gogoulos
2e57164831 req 2025-12-30 17:55:23 +02:00
Markos Gogoulos
de0c16729b a 2025-12-30 15:43:42 +02:00
Markos Gogoulos
2c0bba1427 a 2025-12-30 15:30:08 +02:00
Markos Gogoulos
54a8e41f6d a 2025-12-30 15:19:36 +02:00
Markos Gogoulos
78fb19b464 a 2025-12-30 14:58:01 +02:00
Markos Gogoulos
8e5e7991b7 a 2025-12-30 14:49:40 +02:00
Markos Gogoulos
5cf435eca0 f 2025-12-30 13:50:37 +02:00
Markos Gogoulos
5026ce73da wtv 2025-12-29 20:45:48 +02:00
Markos Gogoulos
8b2ebe2415 wtv 2025-12-29 20:40:48 +02:00
Markos Gogoulos
8df320e134 wtv 2025-12-29 20:40:36 +02:00
Markos Gogoulos
8c8f737460 wtv 2025-12-29 20:32:56 +02:00
Markos Gogoulos
995faedb08 wtv 2025-12-29 20:16:21 +02:00
Markos Gogoulos
bde300b4bd all 2025-12-29 20:14:32 +02:00
Markos Gogoulos
fd5c0a2908 all 2025-12-29 20:04:41 +02:00
Markos Gogoulos
9c145da2e2 all 2025-12-29 20:02:55 +02:00
Markos Gogoulos
e9e5d44c3e wtv 2025-12-29 19:47:03 +02:00
Markos Gogoulos
a624c2e5b8 wtv 2025-12-29 19:46:36 +02:00
Markos Gogoulos
748d3b39ba wtv 2025-12-29 19:42:17 +02:00
Markos Gogoulos
ddc6bf9e67 wtv 2025-12-29 19:36:53 +02:00
Markos Gogoulos
aa7dbfe534 wtv 2025-12-29 19:34:53 +02:00
Markos Gogoulos
5cc72357c6 wtv 2025-12-29 19:33:24 +02:00
Markos Gogoulos
01b061a47b wtv 2025-12-29 19:29:41 +02:00
Markos Gogoulos
fbc78e7944 wtv 2025-12-29 19:23:55 +02:00
Markos Gogoulos
9e7a8afdda wtv 2025-12-29 19:21:26 +02:00
Markos Gogoulos
5572a67019 wtv 2025-12-29 19:16:29 +02:00
Markos Gogoulos
610590972f wtv 2025-12-29 19:15:10 +02:00
Markos Gogoulos
bdf7d3c2d0 wtv 2025-12-29 19:14:50 +02:00
Markos Gogoulos
a47bf5a3f8 wtv 2025-12-29 19:11:02 +02:00
Markos Gogoulos
38caea3c7c wtv 2025-12-29 19:08:44 +02:00
Markos Gogoulos
30491bf420 wtv 2025-12-29 19:06:58 +02:00
Markos Gogoulos
d0ebe19c2a wtv 2025-12-29 19:03:31 +02:00
Markos Gogoulos
59be9f16c0 wtv 2025-12-29 19:01:47 +02:00
Markos Gogoulos
a2d898c54e wtv 2025-12-29 18:59:24 +02:00
Markos Gogoulos
9733d53c0b wtv 2025-12-29 18:57:39 +02:00
Markos Gogoulos
70e2c67f3d wtv 2025-12-29 18:55:44 +02:00
Markos Gogoulos
77721d9c0e wtv 2025-12-29 18:48:35 +02:00
Markos Gogoulos
06bc64b2c4 all 2025-12-29 18:21:44 +02:00
Markos Gogoulos
b9899476b9 this 2025-12-29 17:45:12 +02:00
Markos Gogoulos
107750406e this 2025-12-29 17:43:54 +02:00
Markos Gogoulos
ae4ae5a07e this 2025-12-29 17:41:16 +02:00
Markos Gogoulos
f346a5604c this 2025-12-29 17:34:47 +02:00
Markos Gogoulos
56026a1a96 this 2025-12-29 17:26:19 +02:00
Markos Gogoulos
a88413ce14 this 2025-12-29 17:10:38 +02:00
Markos Gogoulos
9dab3ad858 this 2025-12-29 16:53:23 +02:00
Markos Gogoulos
dfe7e8fab0 this 2025-12-29 16:46:29 +02:00
Markos Gogoulos
1181d16ab9 this 2025-12-29 16:36:53 +02:00
Markos Gogoulos
d032ee3baa this 2025-12-29 16:35:47 +02:00
Markos Gogoulos
93f66d206b this 2025-12-29 14:17:30 +02:00
Markos Gogoulos
0585513439 this 2025-12-29 14:13:45 +02:00
Markos Gogoulos
9667e6b0ad this 2025-12-29 13:57:40 +02:00
Markos Gogoulos
f56948a4a2 this 2025-12-28 16:51:03 +02:00
Markos Gogoulos
8b3e76b554 this 2025-12-28 16:44:43 +02:00
Markos Gogoulos
dc417de628 this 2025-12-28 16:43:00 +02:00
Markos Gogoulos
35cd56c85c this 2025-12-28 16:34:59 +02:00
Markos Gogoulos
f0b2451815 this 2025-12-28 16:18:32 +02:00
Markos Gogoulos
7696251394 doc 2025-12-28 16:13:37 +02:00
Markos Gogoulos
b95725660b notes 2025-12-28 16:07:42 +02:00
Markos Gogoulos
d6bf98b30e this 2025-12-28 15:47:26 +02:00
Markos Gogoulos
3baa8ef7d7 this 2025-12-28 15:41:23 +02:00
Markos Gogoulos
45246eac4f this 2025-12-28 15:41:01 +02:00
Markos Gogoulos
9685c1b5d4 this 2025-12-28 15:39:47 +02:00
Markos Gogoulos
20a1da22bb this 2025-12-28 15:37:35 +02:00
Markos Gogoulos
f9a94321ad this 2025-12-28 15:35:29 +02:00
Markos Gogoulos
f85299a600 this 2025-12-28 15:30:37 +02:00
Markos Gogoulos
29ab2a715b this 2025-12-28 15:23:51 +02:00
Markos Gogoulos
43ce685f08 this 2025-12-28 15:22:08 +02:00
Markos Gogoulos
8c682a76af this 2025-12-28 15:19:49 +02:00
Markos Gogoulos
ec6b6daa81 this 2025-12-28 15:18:55 +02:00
Markos Gogoulos
cf90169240 this 2025-12-28 15:17:52 +02:00
Markos Gogoulos
fb3f377e27 this 2025-12-28 15:16:23 +02:00
Markos Gogoulos
f5f9a7beac this 2025-12-28 15:14:14 +02:00
Markos Gogoulos
726a5b74a1 this 2025-12-28 15:12:49 +02:00
Markos Gogoulos
40c31f295a this 2025-12-28 15:11:24 +02:00
Markos Gogoulos
1d77293afc this 2025-12-28 15:10:19 +02:00
Markos Gogoulos
5c702387ca this 2025-12-28 15:09:22 +02:00
Markos Gogoulos
0001f370a9 this 2025-12-28 15:05:40 +02:00
Markos Gogoulos
af71d4c906 this 2025-12-28 15:03:58 +02:00
Markos Gogoulos
eb7503125d this 2025-12-28 15:02:57 +02:00
Markos Gogoulos
f897d0ba2b this 2025-12-28 15:00:14 +02:00
Markos Gogoulos
545cca154e this 2025-12-28 14:39:04 +02:00
Markos Gogoulos
ef4ff9cb1d this 2025-12-28 14:33:51 +02:00
Markos Gogoulos
3a40fc6d88 this 2025-12-28 14:31:19 +02:00
Markos Gogoulos
f67d2a4d78 erq 2025-12-24 17:28:27 +02:00
Markos Gogoulos
295578dae2 lti 2025-12-24 17:28:12 +02:00
Markos Gogoulos
ed5cfa1a84 add icon on media profile page 2025-12-24 17:18:30 +02:00
Markos Gogoulos
2fe48d8522 fix formatting 2025-12-24 12:29:25 +02:00
Josh Preston
90331f3b4a Fix: Add regex denoter and improve celerybeat gitignore (#1446)
* (bugfix): Added celerybeat extras to gitignore

* (bugfix): fixed missing regex denoter

* Fix .dockerignore node_modules pattern and add comprehensive exclusions

- Fix #1398: Change 'node_modules' to '**/node_modules' to exclude all nested directories
- Add patterns for Python bytecode, IDE files, logs, and build artifacts
- Consolidate node_modules patterns in .gitignore to use **/node_modules/
2025-12-24 12:28:55 +02:00
Josh Preston
c57f528ab1 Add missing migration for Meta options on Subtitle, TranscriptionRequest, and VideoTrimRequest (#1448)
Fixes #1447

This migration adds the missing AlterModelOptions operations for:
- Subtitle model (verbose_name: 'Caption', verbose_name_plural: 'Captions')
- TranscriptionRequest model (verbose_name: 'Caption Request', verbose_name_plural: 'Caption Requests')
- VideoTrimRequest model (verbose_name: 'Trim Request', verbose_name_plural: 'Trim Requests')

These Meta options were defined in the models but never migrated, causing
makemigrations --dry-run to show pending migrations on fresh clones.
2025-12-24 12:18:48 +02:00
Markos Gogoulos
fa67ffffb4 replace media, shared state, better category options 2025-12-24 12:14:01 +02:00
Markos Gogoulos
872571350f static files 2025-12-22 11:14:35 +02:00
Markos Gogoulos
665971856b version bump 2025-12-22 11:12:37 +02:00
Yiannis Christodoulou
d9b1d6cab1 feat: Improve Visual Distinction Between Trim and Chapters Editors (#1445)
* Update .gitignore

* feat: Improve Visual Distinction Between Trim and Chapters Editors

* fix: Convert timeline header styles to CSS classes

Moved inline styles for timeline headers in chapters and video editors to dedicated CSS classes for better maintainability and consistency.

* Bump version to 7.3.0

Update the VERSION in cms/version.py to 7.3.0 for the new release.

* build assets

* Update segment color schemes in video and chapters editor.

* build assets

* build assets

* fix: Prevent Safari from resetting segments after drag operations

Prevent Safari from resetting segments when loadedmetadata fires multiple times and fix stale state issues in click handlers by using refs instead of closure variables.

* build assets

* Bump version to 7.3.0-beta.3

Update the VERSION string in cms/version.py to reflect the new pre-release version 7.3.0-beta.3.
2025-12-22 11:12:19 +02:00
Markos Gogoulos
aeef8284bf docs: update page link 2025-12-01 11:29:58 +02:00
Markos Gogoulos
a90fcbf8dd version bump 2025-11-21 12:30:12 +02:00
Markos Gogoulos
1b3cdfd302 fix: add delay to task creation 2025-11-21 12:30:05 +02:00
Yiannis Christodoulou
cd7dd4f72c fix: Chapter numbering and preserve custom titles on segment reorder (#1435)
* FIX: Preserve custom chapter titles when renumbering (151)

Updated the renumberAllSegments function to only update chapter titles that match the default 'Chapter X' pattern, preserving any custom titles. Also ensured segments are renumbered after updates for consistent chronological naming.

* build assets (chapters editor)
2025-11-21 12:29:19 +02:00
Markos Gogoulos
9b3d9fe1e7 trim (#1431) 2025-11-13 12:42:48 +02:00
Markos Gogoulos
ea340b6a2e V7 f4 (#1430) 2025-11-13 12:30:25 +02:00
Markos Gogoulos
ba2c31b1e6 fix: static files (#1429) 2025-11-12 14:08:02 +02:00
Yiannis Christodoulou
5eb6fafb8c fix: Show default chapter names in textarea instead of placeholder text (#1428)
* Refactor chapter filtering and auto-save logic

Simplified chapter filtering to only exclude empty titles, allowing default chapter names. Updated auto-save logic to skip saving when there are no chapters or mediaId. Removed unused helper function and improved debug logging.

* Show default chapter title in editor and set initial title

The chapter title is now always displayed in the textarea, including default names like 'Chapter 1'. Also, the initial segment is created with 'Chapter 1' as its title instead of an empty string for better clarity.

* build assets
2025-11-12 14:04:07 +02:00
Markos Gogoulos
c035bcddf5 small 7.2.x fixes 2025-11-11 19:51:42 +02:00
Markos Gogoulos
01912ea1f9 fix: adjust poster url for audio 2025-11-11 13:21:10 +02:00
Markos Gogoulos
d9f299af4d V7 small fixes (#1426) 2025-11-11 13:15:36 +02:00
Markos Gogoulos
e80590a3aa Bulk actions support (#1418) 2025-11-11 11:32:54 +02:00
Yiannis Christodoulou
2a0cb977f2 Video.js fixes and improvements after major upgrade (#1413) 2025-10-27 11:53:35 +02:00
Yiannis Christodoulou
a5e6e7b9ca feat: Major Upgrade to Video.js v8 — Chapters Functionality, Fixes and Improvements 2025-10-20 15:30:00 +03:00
Markos Gogoulos
b39072c8ae feat: disapprove user 2025-10-09 17:29:29 +03:00
Markos Gogoulos
f4ab60e894 fix: extend gitignore 2025-09-26 11:33:44 +03:00
Markos Gogoulos
8656b40c5b fix: url 2025-09-25 08:51:26 +03:00
Markos Gogoulos
553a25a86f feat: pass extra css (#1392) 2025-09-24 19:01:06 +03:00
Markos Gogoulos
1c1af489f1 feat: allow portal logo override (#1391) 2025-09-24 16:26:45 +03:00
Markos Gogoulos
c4c5ecf06a update version 2025-09-21 16:18:31 +03:00
Markos Gogoulos
725cc71960 fix migration 2025-09-21 16:17:18 +03:00
Markos Gogoulos
0c1c5bbb09 add new migration 2025-09-21 16:15:46 +03:00
Markos Gogoulos
56182f0a6d feat: allow customizable about page 2025-09-21 15:47:04 +03:00
Markos Gogoulos
208f0b338b feat: update versions for python packages, add Pages functionality (#1386)
This PR updates Django core version and also brings html pages support (that admins can create)
2025-09-21 15:38:43 +03:00
Markos Gogoulos
cbef629baf feat: approve users, edit users through manage users page (#1383) 2025-09-20 15:16:52 +03:00
Bret.S (AKA: CyberGladius)
8e8454d8c2 Docs Update with SAML Deployement Guide and Troubleshooting (#1377)
* Docs update with SAML deployment guide.

* Docs update with SAML deployment guide. URL Fix

---------

Co-authored-by: root <git@tdcme.com>
2025-09-16 14:51:05 +03:00
Markos Gogoulos
8d982ace92 Feat whisper opts (#1368) 2025-09-04 13:39:41 +03:00
Meet Dholakia
6cee02085c Updated the title splicing length to 100 in playlist, category and media models (#1364) 2025-09-02 19:43:46 +03:00
Markos Gogoulos
e33aa17911 fix version 2025-09-02 11:32:02 +03:00
Markos Gogoulos
a8db23f204 docs: add whisper section 2025-09-02 11:00:17 +03:00
Markos Gogoulos
d6428e3334 version bump 2025-09-01 20:34:15 +03:00
Markos Gogoulos
fd342fd1d6 version bump 2025-09-01 20:32:11 +03:00
Markos Gogoulos
7a1b32f1ba fix: delete media (#1366) 2025-09-01 20:06:49 +03:00
Markos Gogoulos
817e16ac60 feat: whisper STT and record screen (#1363) 2025-09-01 15:11:38 +03:00
Markos Gogoulos
8cbeb72dd2 feat: add fily type and max user media uplod limits 2025-08-19 11:35:49 +03:00
Markos Gogoulos
e9f862a0ff feat: 3 small fixes (#1347)
* fix: datetime input

* show message on upload user only

* show all media of user for editors/managers/admins
2025-08-17 19:18:47 +03:00
Markos Gogoulos
02eac68b51 fix: swagger (#1345)
* fix: swagger

* fix: swagger
2025-08-12 12:24:26 +03:00
Markos Gogoulos
e790795bfd feat: bulk actions API 2025-08-07 13:21:12 +03:00
Markos Gogoulos
de99d84c18 feat: revert ffmpeg install 2025-07-07 13:33:45 +03:00
Markos Gogoulos
8aa89c0958 feat: use apt for installing ffmpeg 2025-07-07 13:00:33 +03:00
Markos Gogoulos
df98b65704 feat: pass version on static files (#1318) 2025-07-07 11:54:02 +03:00
Markos Gogoulos
a607996bfa feat: adds minimum resolution of 144p 2025-07-07 11:34:02 +03:00
Markos Gogoulos
79f2e2bb11 feat: replace format with fstrings 2025-07-07 11:26:08 +03:00
Markos Gogoulos
d54732040a feat: add DB connection pooling 2025-07-07 11:18:40 +03:00
Andy
e8520bc7cd fix: date picker in edit media (#1297)
By default, Django uses type=text for the date input, which respects
the locale settings. However, when changing the input type to date,
it should only accept YYYY-MM-DD format so the input field can be
properly handled.
2025-07-06 11:44:44 +03:00
Markos Gogoulos
b6e46e7b62 feat: replace login middleware (#1314) 2025-07-06 11:25:50 +03:00
Adam Stradovnik
36eab954bd feat: Adds support for Slovenian frontend translations (#1306) 2025-07-06 11:05:07 +03:00
Markos Gogoulos
610716533b fix formatting 2025-07-01 15:46:34 +03:00
Yiannis Christodoulou
4f1c4a2b4c fix: Disable Segment Tools and Reset Preview State During Playback (#1305)
* fix: Disable Segment Tools and Reset Preview State During Playback

* chore: remove some unnecessary comments

* chore: build assets

* fix: do not display the handles (left/right) on preview mode

* fix: Disable all tools on preview mode (undo, redo, reset, etc.)

* Update README.md

* feat: Prettier configuration for video editor

* Update README.md

* Update .prettierrc

* style: Format entire codebase (video-editor) with Prettier

* fix: During segments playback mode, disable button interactions but keep hover working

* feat: Add yarn format

* prettier format

* Update package.json

* feat: Install prettier and improve formatting

* build assets

* Update version.py 6.2.0
2025-07-01 15:33:39 +03:00
Markos Gogoulos
83f3eec940 feat: enable editing of media slug, show categories on manage media 2025-06-24 11:13:33 +03:00
Markos Gogoulos
a5acce4ab1 fix: single server install issues 2025-06-15 11:19:29 +03:00
Ofek
a4e9309350 (FEAT) Add Hebrew Translation (#1295) 2025-06-14 11:08:17 +03:00
Casper Tollund
6beaf0bbe2 Add support for Danish translation (#1293)
Co-authored-by: Casper Tollund <casto@mac.ait.clients.local>
2025-06-12 17:35:37 +03:00
Andy
70168299ba Adds support for Traditional Chinese translation (#1282)
* Adds support for Traditional Chinese translation

* Reformats the style to match black formatter
2025-06-11 15:10:49 +03:00
Markos Gogoulos
b28c2d8271 feat: Video Trimmer and more 2025-06-11 14:48:30 +03:00
angy91m
d34fc328bf feat: IT traslation added (#1267)
* Create it.py

* feat: IT traslation added
2025-05-15 13:30:07 +03:00
Markos Gogoulos
ab4d9d67df fix: issue with import (#1245) 2025-04-07 18:36:38 +03:00
Markos Gogoulos
f7a2f049bd Update README.md (#1244) 2025-04-05 12:52:29 +03:00
Markos Gogoulos
05414f66c7 feat: RBAC + SAML support 2025-04-05 12:44:21 +03:00
Markos Gogoulos
8fecccce1c feat: move docker compose files 2025-03-18 19:21:39 +02:00
Markos Gogoulos
2a7123ca0b feat: move docker compose files in dir (#1231) 2025-03-18 19:05:04 +02:00
Yiannis Christodoulou
20f305e69e feat: Frontend Dependencies Upgrade +Fix Timestamps in videos 2025-03-18 19:01:50 +02:00
Markos Gogoulos
d1fda05fdc fix: flake8 2025-03-09 20:50:07 +02:00
Markos Gogoulos
a02e0a8a66 fix: flake8 2025-03-09 20:48:09 +02:00
Markos Gogoulos
21f76dbb6e feat: playlist optimizations (#1216) 2025-03-09 20:44:04 +02:00
Markos Gogoulos
50e9f3103f feat: better support for subtitles (#1215) 2025-03-09 20:29:26 +02:00
Markos Gogoulos
0b9a203123 revert head changes 2025-02-13 20:31:19 +02:00
Sven-Thorsten Dietrich
5cbd815496 fix: Fix Docker WARN: FromAsCasing (#1196)
Fixes: L27 'as' and 'FROM' keywords' casing do not match

Signed-off-by: Sven-Thorsten Dietrich <thebigcorporation@gmail.com>
2025-02-13 13:57:14 +02:00
Markos Gogoulos
3a8cacc847 feat: Bulk fixes (#1195)
remove ckeditor - not in use
add more strict default password validators
set Django admin as configurable URL
add nginx HSTS and CSP headers
enable moving from private to unlisted in the PORTAL_WORKFLOW private
on default comments listing, show only comments for public media
in case of a private media, dont expose any unneeded metadata
2025-02-13 13:41:53 +02:00
Markos Gogoulos
5402ee7bc5 fix: crispy forms (#1194) 2025-02-12 14:27:27 +02:00
Markos Gogoulos
a6a2b50c8d remove redundant message 2025-02-10 22:25:24 +02:00
Markos Gogoulos
23e48a8bb7 remove redundant message 2025-02-10 22:24:42 +02:00
Markos Gogoulos
313cd9cbc6 fix issue with static files in dev 2025-02-10 22:04:14 +02:00
Markos Gogoulos
0392dbe1ed feat: lib updates and more (#1187)
This PR performs the following changes:

- update python in Dockerfile
- updates all python libraries (including Django)
- makes md5sum calculation redundant by default
- updates Postgresql used in Docker. For new installations this is ok. For existing ones this is backwards incompatible, and restore through pg_dump+pg_restore has to be performed in order to utilize Postgres 17 (since its not compatible with previous versions and will complain)
- fixes issues with HLS, and adds test to ensure they won't happen again
- allows . and @ in usernames
2025-02-10 11:34:00 +02:00
Markos Gogoulos
a7562c244e fix: black (#1164) 2025-01-22 20:16:21 +02:00
Sonu Kumar
d2ee12087c feat: Re-Arranged Subtitles by first letter of language (#863) 2025-01-22 19:25:47 +02:00
aleensd
6db01932e1 feat: fullscreen images slideshow (#1120) 2025-01-22 19:02:01 +02:00
Markos Gogoulos
53d8215346 feat: comment URL (#1163) 2025-01-22 14:55:43 +02:00
David
1b960b28f8 feat: Update insert login_required middleware to allow auth middleware (#1082) 2025-01-22 14:54:25 +02:00
Markos Gogoulos
02d9188aa1 feat: increase task limit (#1161) 2025-01-22 11:22:07 +02:00
Meng Sen
8d9a4618f0 feat: support for reading environment variables on Docker 2024-11-22 18:35:47 +02:00
aleensd
cf93a77802 feat: integrate react-pdf-viewer for PDF display (#1112) 2024-11-21 12:15:25 +02:00
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
Markos Gogoulos
4992cc425c feat: translations support 2024-10-04 13:17:40 +03:00
Tudorel Oprisan
ef4067cbdd fix: replaced pipe with empty string on helper function 2024-10-02 19:16:18 +03:00
Markos Gogoulos
8cc3513a8a docs 2024-10-02 15:57:19 +03:00
Kyle Maas
90e593946d feat: allow commenting by regular users when posting media requires advanced permissions (#1023) 2024-10-02 15:52:30 +03:00
Markos Gogoulos
f7136e2a11 isort 2024-10-02 12:54:47 +03:00
Markos Gogoulos
0151e834a1 black formatting 2024-10-02 12:53:48 +03:00
Markos Gogoulos
5fe4d3a9fc feat: rounded corners 2024-10-02 11:33:17 +03:00
Markos Gogoulos
94c646fdb8 update metadata only, on API call 2024-09-20 19:26:13 +03:00
Markos Gogoulos
d665058b80 speed up docker start 2024-09-20 13:02:00 +03:00
Markos Gogoulos
986c7d1074 add validation to files uploading to avoid client side pushing arbitrary data (#1057) 2024-09-20 13:01:33 +03:00
thau0x01
1adee8c156 fix #943 (#1052) 2024-09-20 12:53:56 +03:00
makerduck
ffd7a52863 Fix postgres role output (#1029) 2024-09-20 12:52:50 +03:00
Kyle Maas
c5047d8df8 Fix null bug in More Options button (#913) 2023-11-14 09:24:05 +02:00
Markos Gogoulos
dcbfaca91c Developer Experience (#911)
local dev environment
2023-11-13 11:13:08 +02:00
Kyle Maas
918df010f5 Fix bug that crashes page if an encoding has a null URL (#912) 2023-11-13 11:09:16 +02:00
Markos Gogoulos
e9739bab45 Feat celery run (#860)
* avoid calling post save signals
* remove stale celery ids that prevent new services from starting
2023-11-10 16:06:17 +02:00
Kyle Maas
e7ce9ef5c0 Add admin action to generate missing encodings for a particular Media (#883)
* Add admin action to generate missing encodings for a particular Media
* Only regenerate the encodings that are missing
2023-11-10 15:41:20 +02:00
Kyle Maas
4829adf110 Add useful fields to the Encodings admin screen (#885) 2023-11-10 15:37:40 +02:00
lavirez
fdff0811a1 cli.py missing f string (#877) 2023-11-10 15:09:22 +02:00
Kyle Maas
92c0ff579a Add sitemap (#572)
add sitemap.xml
2023-11-10 15:03:36 +02:00
Markos Gogoulos
847cff2b5c license section 2023-11-10 14:30:14 +02:00
Markos Gogoulos
e8d3ff25be Disable encoding and show only original file (#829)
Disable encoding and show only original file #829
2023-11-10 14:25:10 +02:00
Markos Gogoulos
15d217453b Update admins_docs.md (#842) 2023-07-17 16:47:06 +03:00
Markos Gogoulos
029665145e Python requirements and Docker version upgrades (#826)
v3.0.0: Python, Django, Celery and other version upgrades
2023-07-03 13:40:39 +03:00
Adi
487e098b96 Upgrade postgres docker compose (#749)
* Upgrade PG to latest stable version alpine
2023-07-03 13:39:15 +03:00
Markos Gogoulos
fe7427a1f2 check resolution for HLS (#832) 2023-07-03 12:19:23 +03:00
Markos Gogoulos
4bf41fe80e fix issue with AVI not being recognised as videos (#833) 2023-07-03 12:18:24 +03:00
Markos Gogoulos
1fd04ca947 pass secrets to workflow 2023-06-28 15:32:32 +03:00
Markos Gogoulos
a1962d4b32 add secret 2023-06-27 18:22:37 +03:00
Markos Gogoulos
6e9c9ed81f add secret 2023-06-27 18:20:30 +03:00
Markos Gogoulos
51186e3253 add secret 2023-06-27 18:16:01 +03:00
Markos Gogoulos
150967b342 add secret 2023-06-27 18:14:56 +03:00
Markos Gogoulos
bb6244d862 trigger build 2023-06-27 18:07:14 +03:00
Markos Gogoulos
a002422b77 update version for workflow 2023-06-27 17:50:16 +03:00
Markos Gogoulos
24167b9624 CI fix branch 2023-06-27 17:30:51 +03:00
Markos Gogoulos
b9db1a5e2e Update README.md (#823)
* Update README.md
2023-06-27 17:26:54 +03:00
Markos Gogoulos
296aeac567 Update admins_docs.md 2023-06-27 13:41:58 +03:00
Markos Gogoulos
10c386f886 Update README.md (#822) 2023-06-27 13:02:21 +03:00
Adi
367faaddd1 Add workflow for docker build and push (#750)
* Add workflow for docker build and push
2023-06-26 09:49:37 +03:00
nmlsdev
3d59b87f09 add rhel8 installation script (#792)
* add rhel8 installation script
2023-06-14 15:18:12 +03:00
Markos Gogoulos
5dee41de39 version 2.0.0 2023-06-13 22:45:03 +03:00
Markos Gogoulos
08bba5fc05 remove redundant file 2023-06-13 21:21:27 +03:00
Markos Gogoulos
102414b514 fix issues with comments (#802)
* fix issues with comments
2023-06-13 19:01:52 +03:00
Markos Gogoulos
c866fdd6ba allow tags to contain chars, not only English alphabet (#801)
* allow tags to contain chars, not only English alphabet
2023-06-13 15:41:13 +03:00
Markos Gogoulos
5b601698a4 increase uwsgi buffer-size para, 2023-06-13 12:44:31 +03:00
Markos Gogoulos
f040f73f51 black formatting 2023-06-12 17:22:39 +03:00
Markos Gogoulos
b7a70d92fa fix typo 2023-06-12 17:13:44 +03:00
mostafa hosseini
2f43cef8da add api_url field to search api (#692)
Co-authored-by: Mostafa Hosseini <mostafa.h@rahgosahgroup.com>
2023-06-12 16:42:30 +03:00
Markos Gogoulos
ad633e6fdf simple cookie consent code (#799)
* simple cookie consent code
2023-06-12 16:40:53 +03:00
Stella
cd8d0ea49a Allow password reset & email verify pages on global login required (#790) 2023-05-31 16:36:31 +03:00
Markos Gogoulos
a3997bfb1c update versions for pre-commit (#741) 2023-03-14 14:09:52 +02:00
Markos Gogoulos
4b0718c43f version 2.0 2023-03-14 13:30:26 +02:00
Markos Gogoulos
91d8179fa0 improve formating on Readme 2023-03-14 13:15:17 +02:00
Markos Gogoulos
6532b19849 Update README.md 2023-03-14 13:13:55 +02:00
Markos Gogoulos
6ea8fd12a3 fix issue with uninitialized video player 2023-03-14 13:13:15 +02:00
Markos Gogoulos
d971bb955f pin versions (#718) 2023-02-17 12:46:45 +02:00
Markos Gogoulos
b52b008f89 enable cors for media dir (#701) 2023-02-17 11:51:48 +02:00
MICRUFUN
30cf5d7176 How to modify encode profiles (#682) 2023-01-03 12:33:38 +02:00
masavini
6fd9a7d37f remove zombie thumbnails (#657)
remove zombie thumbnails
2022-12-05 12:31:31 +02:00
Markos Gogoulos
9c6d13559b update doc 2022-11-29 17:24:13 +02:00
Markos Gogoulos
8ec97a8219 pre-commit yaml 2022-11-29 15:27:15 +02:00
Markos Gogoulos
de8f9ca718 fix pre-commit version 2022-11-29 14:55:00 +02:00
Markos Gogoulos
a4bedca4db fix pre-commit version 2022-11-29 14:53:05 +02:00
Kyle Maas
da565b3bfc Fix double slashes in URIs (#558) 2022-11-29 11:22:30 +02:00
Kyle Maas
239ff6cb60 Add meta description tag for search engines (#551) 2022-11-29 11:02:16 +02:00
masavini
da840b156d add api path to LOGIN_REQUIRED_IGNORE_PATHS (#483) 2022-11-29 10:59:36 +02:00
Markos Gogoulos
b08d493823 static files 2022-09-20 12:27:33 +00:00
masavini
25eaa35758 Fix admin user creation (#472)
add ADMIN_USER, ADMIN_PASSWORD and ADMIN_EMAIL
2022-09-20 15:18:25 +03:00
MrPercheul
cba2ed75ed Show comments in the Timebar (#442)
Show comments in the Timebar
2022-09-20 15:16:16 +03:00
Markos Gogoulos
de5bd07a90 Feat videojs update (#519)
update video.js to 7.20.2
2022-09-07 12:33:11 +03:00
Markos Gogoulos
34c1a4e33e static files (#506) 2022-08-22 17:40:54 +03:00
Markos Gogoulos
80be0d06e5 correctly identify audio (#505)
* correctly identify audio
2022-08-22 17:35:11 +03:00
masavini
a6322fae6d missing f-strings (#490) 2022-08-12 15:45:35 +03:00
Markos Gogoulos
3d4dd27220 Update user_docs.md 2022-06-01 14:23:42 +03:00
MrPercheul
f6a78dd0b4 Mention users in comments (#429)
Mention users in comments (#429)
2022-06-01 14:23:21 +03:00
DecaTec
cbc9633fe2 Fix pre-commit (#440) 2022-06-01 13:51:46 +03:00
DecaTec
3e7b106482 Vp9 fixes (#438)
* Fix fo ffprobe appending pipe
* Fix media remains pending when only encoding to webm
2022-06-01 13:42:35 +03:00
DecaTec
0f182c2b15 Fixed duplicate 'pk' on fixtures
Fixed duplicate 'pk' on fixtures
2022-06-01 13:21:22 +03:00
Markos Gogoulos
1ddfae7c95 update black (#452) 2022-05-10 13:46:22 +03:00
Markos Gogoulos
f1969e4637 pin version of debug toolbar 2022-05-09 21:05:25 +03:00
Markos Gogoulos
9e7a7a7482 Update developers_docs.md 2022-04-27 21:53:11 +03:00
Markos Gogoulos
6e478e6e82 fix typo on documentation 2022-03-23 20:48:26 +02:00
Behnam B.Marandi
e06deed3b8 Add configuration for only member access to media (#424)
I added an entry under 'Configuration 'per discussion in the Q&A section:
https://github.com/mediacms-io/mediacms/discussions/422
2022-03-23 20:47:06 +02:00
Markos Gogoulos
f8376c5c58 static files (#432) 2022-03-23 19:04:59 +02:00
MrPercheul
e7ae2833d9 Timestamp (#430)
* Test

Messing around to figure out docker and whatnot

* Revert "Test"

This reverts commit 43b76932c5.

* Update docker dev yaml

Update the docker to the environement with the latest version of selenium (and their new arguments), and to be on firefox

* Timestamp on play

Adds the a one time event that jumps to a timestamp if there is a t parameter in the URL

* Timestamp when sharing DRAFT

First draft to show the timestamp when sharing. Missing checkbox and appending the get param to shown link

* Checkbox and update share url

Checkbox show a clean timestamp and the media URL updates with the correct timestamp upon selection

* Cleaning before PR

removing un-necessary modified  files

* Clean up before PR - remove statics

* Timestamp in comments

Parse the comments to wrap timestamps with an appropriate anchor

* Forgotten comments and console.logs

* Last touch for PR

- Cleaning media.js for PR
- Using  MediaPageStore instead of window.location when wrapping the timestamp in comments

* Screenshot

Adding the screenshot for the user_docs

* PR amends

Amending VideoPlayer componnent to take check if the Get param 't' is a number, and to keep it within the duration of the video.
Required to change the listener from 'play' to 'loadedmetadata' to have access to the video duration (otherwise it was too early)

Also changed the User_doc file to inform users of the timestamp function
2022-03-23 17:38:02 +02:00
Markos Gogoulos
fb0f3ee739 dislike button fix (#411) 2022-02-03 18:44:56 +02:00
Markos Gogoulos
c0701de047 reverting image logo 2022-01-31 21:44:43 +02:00
Shubhank Saxena
0d4918a715 CLI API Tool (#273)
CLI tool
2022-01-31 21:12:29 +02:00
Megidd
8093c4ccb5 Fix logo height (#348)
* Set logo height to 100%
2022-01-31 21:07:02 +02:00
Megidd
2dbd97cb22 Fix dislike count (#388) 2022-01-31 21:05:37 +02:00
Markos Gogoulos
6b6662420f adds instructions to get coverage (#299)
Co-authored-by: Markos Gogoulos <markos@orfium.com>
2022-01-31 20:53:57 +02:00
sthierolf
f1a1e342db Update install.sh (#303)
Tested with newly release Debian 11 Bullseye, added to OS Version check.
2021-10-14 10:24:07 +03:00
Markos Gogoulos
738247c32b pin PG version on docker compose files (#300) 2021-10-12 21:07:45 +03:00
Markos Gogoulos
f974d78270 improve validation on text indexing, for search (#294) 2021-10-01 17:49:41 +03:00
multiflexi
28031f07e5 Filters (#289)
* Framerate fix

Keep original framerate up to 60fps, halve any framerate above 60fps. Because of "video_frame_rate": Fraction(video_info["r_frame_rate"]), it does not work, when float used, the video is encoded but framerate suffers from rounding error.

* Framerate fix

Keep original framerate up to 60fps, halve any framerate above 60fps. Because of "video_frame_rate": Fraction(video_info["r_frame_rate"]), it does not work, when float used, the video is encoded but framerate suffers from rounding error.

* Introduction of minimum bitrate modifier

A minimum bitrate modifier introduced as per https://developers.google.com/media/vp9/settings/vod

* Introduction of minimum bitrate modifier

A minimum bitrate modifier introduced as per https://developers.google.com/media/vp9/settings/vod

* Deinterlacing and better filter logic
2021-10-01 17:48:52 +03:00
Markos Gogoulos
4480fa7de1 documentation, admin section, plus fix of a typo on models (#293)
* documentation, admin section, plus fix of a typo on models
2021-09-27 15:07:17 +03:00
Markos Gogoulos
32e07035f3 add tests for new uploads (#291)
* add tests for new uploads
2021-09-23 18:34:27 +03:00
Markos Gogoulos
2ce8dba163 Feat fix precommit (#280)
* add version file, fix pre-commit
2021-08-20 14:16:19 +03:00
multiflexi
48838ac406 Framerate fix (#246)
* Framerate fix

Keep original framerate up to 60fps, halve any framerate above 60fps. Because of "video_frame_rate": Fraction(video_info["r_frame_rate"]), it does not work, when float used, the video is encoded but framerate suffers from rounding error.

* Framerate fix

Keep original framerate up to 60fps, halve any framerate above 60fps. Because of "video_frame_rate": Fraction(video_info["r_frame_rate"]), it does not work, when float used, the video is encoded but framerate suffers from rounding error.

* Introduction of minimum bitrate modifier

A minimum bitrate modifier introduced as per https://developers.google.com/media/vp9/settings/vod

* Introduction of minimum bitrate modifier

A minimum bitrate modifier introduced as per https://developers.google.com/media/vp9/settings/vod
2021-08-20 14:04:50 +03:00
Markos Gogoulos
062e4be6c2 small typo (#277) 2021-08-20 13:57:55 +03:00
Markos Gogoulos
fb00f94bfa add more tests (#268)
* add more tests as STUBS
2021-08-17 00:01:52 +03:00
Markos Gogoulos
755df50c41 pin Django 3.1.12 (#260)
* pin Django 3.1.12, this requires to remove absolute paths for upload dirs
2021-08-16 23:46:25 +03:00
Markos Gogoulos
ba94989e6a Feat urls (#257)
add new URLS, add swaggger doc, add tests
2021-08-05 13:25:25 +03:00
Markos Gogoulos
86cc0442d8 move envs from Dockerfile to docker-compose files (#264) 2021-08-02 22:03:32 +03:00
Markos Gogoulos
2dde4257f7 remove smoke test that breaks (#263) 2021-08-02 19:14:23 +03:00
swiftugandan
3afff52ebf restore /tmp that was deleted while adding static ffmpeg (#262) 2021-08-02 18:40:12 +03:00
1146 changed files with 126035 additions and 160740 deletions

4
.coveragerc Normal file
View File

@@ -0,0 +1,4 @@
[run]
omit =
*bento4*
*/migrations/*

View File

@@ -1,2 +1,69 @@
node_modules
npm-debug.log
# Node.js/JavaScript dependencies and artifacts
**/node_modules
**/npm-debug.log*
**/yarn-debug.log*
**/yarn-error.log*
**/.yarn/cache
**/.yarn/unplugged
**/package-lock.json
**/.npm
**/.cache
**/.parcel-cache
**/dist
**/build
**/*.tsbuildinfo
# Python bytecode and cache
**/__pycache__
**/*.py[cod]
**/*$py.class
**/*.so
**/.Python
**/pip-log.txt
**/pip-delete-this-directory.txt
**/.pytest_cache
**/.coverage
**/htmlcov
**/.tox
**/.mypy_cache
**/.ruff_cache
# Version control
**/.git
**/.gitignore
**/.gitattributes
# IDE and editor files
**/.DS_Store
**/.vscode
**/.idea
**/*.swp
**/*.swo
**/*~
# Logs and runtime files
**/logs
**/*.log
**/celerybeat-schedule*
**/.env
**/.env.*
# Media files and data directories (should not be in image)
media_files/**
postgres_data/**
pids/**
# Static files collected at runtime
static_collected/**
# Documentation and development files
**/.github
**/CHANGELOG.md
# Test files and directories
**/tests
**/test_*.py
**/*_test.py
# Frontend build artifacts (built separately)
frontend/dist/**

20
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
---
name: "CI"
on:
pull_request:
push:
branches:
- main
paths-ignore:
- '**/README.md'
jobs:
pre-commit:
uses: ./.github/workflows/pre-commit.yml
test:
uses: ./.github/workflows/python.yml
needs: [pre-commit]
release:
uses: ./.github/workflows/docker-build-push.yml
secrets: inherit # pass all secrets
needs: [test]
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'

78
.github/workflows/docker-build-push.yml vendored Normal file
View File

@@ -0,0 +1,78 @@
name: Docker build and push
on:
workflow_call:
push:
tags:
- v*.*.*
jobs:
release:
name: Build & release to DockerHub
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta for base image
id: meta-base
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
labels: |
org.opencontainers.image.title=MediaCMS
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Docker meta for full image
id: meta-full
uses: docker/metadata-action@v4
with:
images: |
mediacms/mediacms
tags: |
type=raw,value=full,enable=${{ github.ref == format('refs/heads/{0}', 'main') }}
type=semver,pattern={{version}}-full
type=semver,pattern={{major}}.{{minor}}-full
type=semver,pattern={{major}}-full
labels: |
org.opencontainers.image.title=MediaCMS Full
org.opencontainers.image.description=MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API. This is the full version with additional dependencies.
org.opencontainers.image.vendor=MediaCMS
org.opencontainers.image.url=https://mediacms.io/
org.opencontainers.image.source=https://github.com/mediacms-io/mediacms
org.opencontainers.image.licenses=AGPL-3.0
- name: Build and push full image
uses: docker/build-push-action@v4
with:
context: .
target: full
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-full.outputs.tags }}
labels: ${{ steps.meta-full.outputs.labels }}
- name: Build and push base image
uses: docker/build-push-action@v4
with:
context: .
target: base
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta-base.outputs.tags }}
labels: ${{ steps.meta-base.outputs.labels }}

View File

@@ -0,0 +1,42 @@
name: Frontend build and test
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
strategy:
matrix:
os: [ubuntu-latest]
node: [20]
runs-on: ${{ matrix.os }}
name: '${{ matrix.os }} - node v${{ matrix.node }}'
permissions:
contents: read
defaults:
run:
working-directory: ./frontend
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- name: Install dependencies
run: npm install
- name: Build script
run: npm run dist
- name: Test script
run: npm run test

View File

@@ -1,15 +0,0 @@
on:
pull_request:
push:
branches:
- main
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: pre-commit/action@v2.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}

15
.github/workflows/pre-commit.yml vendored Normal file
View File

@@ -0,0 +1,15 @@
name: pre-commit
on:
workflow_call:
jobs:
pre-commit:
name: Pre-Commit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
- uses: pre-commit/action@v3.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,14 +1,11 @@
name: Python Tests
on:
pull_request:
push:
branches:
- main
workflow_call:
jobs:
build:
name: Build & test via docker-compose
runs-on: ubuntu-latest
steps:
@@ -16,10 +13,10 @@ jobs:
uses: actions/checkout@v1
- 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
run: docker-compose -f docker-compose-dev.yaml up -d
run: docker compose -f docker-compose-dev.yaml up -d
- name: List containers
run: docker ps
@@ -29,7 +26,10 @@ jobs:
shell: bash
- name: Run Django Tests
run: docker-compose -f docker-compose-dev.yaml exec -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
- name: Tear down the Stack
run: docker-compose -f docker-compose-dev.yaml down
run: docker compose -f docker-compose-dev.yaml down

24
.gitignore vendored
View File

@@ -1,10 +1,14 @@
cli-tool/.env
frontend/package-lock.json
media_files/encoded/
media_files/original/
media_files/hls/
media_files/chunks/
media_files/uploads/
media_files/tinymce_media/
media_files/userlogos/
postgres_data/
celerybeat-schedule
celerybeat-schedule*
logs/
pids/
static/admin/
@@ -15,3 +19,21 @@ static/rest_framework/
static/drf-yasg
cms/local_settings.py
deploy/docker/local_settings.py
yt.readme.md
# Node.js dependencies (covers all node_modules directories, including frontend-tools)
**/node_modules/
/static_collected
/frontend-tools/video-editor-v1
frontend-tools/.DS_Store
static/video_editor/videos/sample-video-30s.mp4
static/video_editor/videos/sample-video-37s.mp4
/frontend-tools/video-editor-v2
.DS_Store
static/video_editor/videos/sample-video-10m.mp4
static/video_editor/videos/sample-video-10s.mp4
frontend-tools/video-js/public/videos/sample-video-white.mp4
frontend-tools/video-editor/client/public/videos/sample-video.mp3
frontend-tools/chapters-editor/client/public/videos/sample-video.mp3
static/chapters_editor/videos/sample-video.mp3
static/video_editor/videos/sample-video.mp3
templates/todo-MS4.md

View File

@@ -1 +0,0 @@
Swift Ugandan <swiftugandan@gmail.com> <swiftugandan@gmail.com>

View File

@@ -1,15 +1,16 @@
repos:
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.9
- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/isort
rev: 5.5.4
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 20.8b1
rev: 23.1.0
hooks:
- id: black
language_version: python3
language_version: python3
additional_dependencies: [ 'click==8.0.4' ]

3
.prettierignore Normal file
View File

@@ -0,0 +1,3 @@
/templates/cms/*
/templates/*.html
*.scss

21
.prettierrc Normal file
View File

@@ -0,0 +1,21 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf",
"embeddedLanguageFormatting": "auto",
"overrides": [
{
"files": ["*.css", "*.scss"],
"options": {
"singleQuote": false
}
}
]
}

View File

@@ -1,8 +1 @@
Wordgames.gr - https://www.wordgames.gr
Yiannis Stergiou - ys.stergiou@gmail.com
Markos Gogoulos - mgogoulos@gmail.com
Contributors
Swift Ugandan - swiftugandan@gmail.com
Please see https://github.com/mediacms-io/mediacms/graphs/contributors for complete list of contributors to this repository!

View File

@@ -1,72 +1,113 @@
FROM python:3.8-buster AS compile-image
FROM python:3.13.5-slim-bookworm AS build-image
SHELL ["/bin/bash", "-c"]
# Set up virtualenv
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
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 .
RUN pip install -r requirements.txt
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
RUN wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d ../bento4 && \
mv ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* ../bento4/ && \
rm -rf ../bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
rm -rf ../bento4/docs && \
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
############ RUNTIME IMAGE ############
FROM python:3.8-slim-buster as runtime-image
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV ADMIN_USER='admin'
ENV ADMIN_EMAIL='admin@localhost'
#ENV ADMIN_PASSWORD='uncomment_and_set_password_here'
# See: https://github.com/celery/celery/issues/6285#issuecomment-715316219
ENV CELERY_APP='cms'
# Use these to toggle which processes supervisord should run
ENV ENABLE_UWSGI='yes'
ENV ENABLE_NGINX='yes'
ENV ENABLE_CELERY_BEAT='yes'
ENV ENABLE_CELERY_SHORT='yes'
ENV ENABLE_CELERY_LONG='yes'
ENV ENABLE_MIGRATIONS='yes'
# Set up virtualenv
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
COPY --chown=www-data:www-data --from=compile-image /home/mediacms.io /home/mediacms.io
RUN apt-get update -y && apt-get -y upgrade && apt-get install --no-install-recommends \
supervisor nginx imagemagick procps wget xz-utils -y && \
# Install system dependencies needed for downloading and extracting
RUN apt-get update -y && \
apt-get install -y --no-install-recommends wget xz-utils unzip && \
rm -rf /var/lib/apt/lists/* && \
apt-get purge --auto-remove && \
apt-get clean
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz && \
mkdir -p tmp && \
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C tmp && \
cp -v tmp/ffmpeg tmp/ffprobe tmp/qt-faststart /usr/local/bin && \
rm -rf tmp ffmpeg-release-amd64-static.tar.xz
RUN wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
RUN mkdir -p ffmpeg-tmp && \
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C ffmpeg-tmp && \
cp -v ffmpeg-tmp/ffmpeg ffmpeg-tmp/ffprobe ffmpeg-tmp/qt-faststart /usr/local/bin && \
rm -rf ffmpeg-tmp ffmpeg-release-amd64-static.tar.xz
# Install Bento4 in the specified location
RUN mkdir -p /home/mediacms.io/bento4 && \
wget -q http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip && \
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -d /home/mediacms.io/bento4 && \
mv /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/* /home/mediacms.io/bento4/ && \
rm -rf /home/mediacms.io/bento4/Bento4-SDK-1-6-0-637.x86_64-unknown-linux && \
rm -rf /home/mediacms.io/bento4/docs && \
rm Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
############ BASE RUNTIME IMAGE ############
FROM python:3.13.5-slim-bookworm AS base
SHELL ["/bin/bash", "-c"]
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV CELERY_APP='cms'
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install system dependencies first
RUN apt-get update -y && \
apt-get -y upgrade && \
apt-get install --no-install-recommends -y \
supervisor \
nginx \
imagemagick \
procps \
build-essential \
pkg-config \
zlib1g-dev \
zlib1g \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libpq-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Set up virtualenv first
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && \
cd /home/mediacms.io && \
python3 -m venv $VIRTUAL_ENV
# Copy requirements files
COPY requirements.txt requirements-dev.txt ./
# Install Python dependencies using pip (within virtualenv)
ARG DEVELOPMENT_MODE=False
RUN pip install --no-cache-dir uv && \
uv pip install --no-binary lxml --no-binary xmlsec -r requirements.txt && \
if [ "$DEVELOPMENT_MODE" = "True" ]; then \
echo "Installing development dependencies..." && \
uv pip install -r requirements-dev.txt; \
fi && \
apt-get purge -y --auto-remove \
build-essential \
pkg-config \
libxml2-dev \
libxmlsec1-dev \
libpq-dev
# Copy ffmpeg and Bento4 from build image
COPY --from=build-image /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=build-image /usr/local/bin/ffprobe /usr/local/bin/ffprobe
COPY --from=build-image /usr/local/bin/qt-faststart /usr/local/bin/qt-faststart
COPY --from=build-image /home/mediacms.io/bento4 /home/mediacms.io/bento4
# Copy application files
COPY . /home/mediacms.io/mediacms
WORKDIR /home/mediacms.io/mediacms
# required for sprite thumbnail generation for large video files
COPY deploy/docker/policy.xml /etc/ImageMagick-6/policy.xml
# Set process control environment variables
ENV ENABLE_UWSGI='yes' \
ENABLE_NGINX='yes' \
ENABLE_CELERY_BEAT='yes' \
ENABLE_CELERY_SHORT='yes' \
ENABLE_CELERY_LONG='yes' \
ENABLE_MIGRATIONS='yes'
EXPOSE 9000 80
RUN chmod +x ./deploy/docker/entrypoint.sh
ENTRYPOINT ["./deploy/docker/entrypoint.sh"]
CMD ["./deploy/docker/start.sh"]
############ FULL IMAGE ############
FROM base AS full
COPY requirements-full.txt ./
RUN mkdir -p /root/.cache/ && \
chmod go+rwx /root/ && \
chmod go+rwx /root/.cache/
RUN uv pip install -r requirements-full.txt

View File

@@ -1,16 +0,0 @@
FROM mediacms/mediacms:latest
SHELL ["/bin/bash", "-c"]
# Set up virtualenv
ENV VIRTUAL_ENV=/home/mediacms.io
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
ENV PIP_NO_CACHE_DIR=1
RUN cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
COPY requirements.txt .
COPY requirements-dev.txt .
RUN pip install -r requirements-dev.txt
WORKDIR /home/mediacms.io/mediacms

253
LTI_SETUP.md Executable file
View File

@@ -0,0 +1,253 @@
# MediaCMS LTI 1.3 Integration Setup Guide
This guide walks you through integrating MediaCMS with a Learning Management System (LMS) like Moodle using LTI 1.3.
## 1. Configure MediaCMS Settings
Add these settings to `cms/local_settings.py`:
```python
# Enable LTI integration
USE_LTI = True
# Enable RBAC for course-based access control
USE_RBAC = True
# Your production domain
FRONTEND_HOST = 'https://your-mediacms-domain.com'
ALLOWED_HOSTS = ['your-mediacms-domain.com', 'localhost']
```
**Note:** LTI-specific cookie settings (SESSION_COOKIE_SAMESITE='None', etc.) are automatically applied when `USE_LTI=True`.
## 2. MediaCMS Configuration
### A. Verify HTTPS Setup
Ensure your MediaCMS server is running on HTTPS. LTI 1.3 requires HTTPS for security and iframe embedding.
### B. Register Your LMS Platform
1. Access Django Admin: `https://your-mediacms-domain.com/admin/lti/ltiplatform/`
2. Add new LTI Platform with these settings:
**Basic Info:**
- **Name:** My LMS (or any descriptive name)
- **Platform ID (Issuer):** Get this from your LMS (e.g., `https://mylms.example.com`)
- **Client ID:** You'll get this from your LMS after registering MediaCMS as an external tool
**OIDC Endpoints (get from your LMS):**
- **Auth Login URL:** `https://mylms.example.com/mod/lti/auth.php`
- **Auth Token URL:** `https://mylms.example.com/mod/lti/token.php`
- **Key Set URL:** `https://mylms.example.com/mod/lti/certs.php`
**Deployment IDs:** Add the deployment ID(s) provided by your LMS as a JSON list, e.g., `["1"]`
**Features:**
- ✓ Enable NRPS (Names and Role Provisioning)
- ✓ Enable Deep Linking
- ✓ Auto-create categories
- ✓ Auto-create users
- ✓ Auto-sync roles
### C. Note MediaCMS URLs for LMS Configuration
You'll need these URLs when configuring your LMS:
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
- **OIDC Login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
- **JWK Set URL:** `https://your-mediacms-domain.com/lti/jwks/`
- **Redirection URI:** `https://your-mediacms-domain.com/lti/launch/`
- **Deep Linking URL:** `https://your-mediacms-domain.com/lti/select-media/`
## 3. LMS Configuration (Moodle Example)
### A. Register MediaCMS as External Tool
1. Navigate to: **Site administration → Plugins → Activity modules → External tool → Manage tools**
2. Click **Configure a tool manually** or add new tool
**Basic Settings:**
- **Tool name:** MediaCMS
- **Tool URL:** `https://your-mediacms-domain.com/lti/launch/`
- **LTI version:** LTI 1.3
- **Tool configuration usage:** Show in activity chooser
**URLs:**
- **Public keyset URL:** `https://your-mediacms-domain.com/lti/jwks/`
- **Initiate login URL:** `https://your-mediacms-domain.com/lti/oidc/login/`
- **Redirection URI(s):** `https://your-mediacms-domain.com/lti/launch/`
**Launch Settings:**
- **Default launch container:** Embed (without blocks) or New window
- **Accept grades from tool:** Optional
- **Share launcher's name:** Always ⚠️ **REQUIRED for user names**
- **Share launcher's email:** Always ⚠️ **REQUIRED for user emails**
> **Important:** MediaCMS creates user accounts automatically on first LTI launch. To ensure users have proper names and email addresses in MediaCMS, you **must** set both "Share launcher's name with tool" and "Share launcher's email with tool" to **Always** in the Privacy settings. Without these settings, users will be created with only a username based on their LTI user ID.
**Services:**
- ✓ IMS LTI Names and Role Provisioning (for roster sync)
- ✓ IMS LTI Deep Linking (for media selection)
**Tool Settings (Important for Deep Linking):**
-**Supports Deep Linking (Content-Item Message)** - Enable this to allow instructors to browse and select media from MediaCMS when adding activities
3. Save the tool configuration
### B. Copy Platform Details to MediaCMS
After saving, your LMS will provide:
- Platform ID (Issuer URL)
- Client ID
- Deployment ID
Copy these values back to the LTIPlatform configuration in MediaCMS admin (step 2B above).
### C. Using MediaCMS in Courses
**Option 1: Embed "My Media" view (Default)**
- In a course, add activity → External tool → MediaCMS
- Leave the custom URL blank (uses default launch URL)
- Students/teachers will see their MediaCMS profile in an iframe
**Option 2: Link to a Specific Video**
- Add activity → External tool → MediaCMS
- Activity name: "November 2020 Video" (or any descriptive name)
- In the activity settings, find **"Custom parameters"** (may be under "Privacy" or "Additional Settings")
- Add this parameter:
```
media_friendly_token=abc123def
```
- Replace `abc123def` with your video's token from MediaCMS (found in the URL: `/view?m=abc123def`)
- Students clicking this activity will go directly to that specific video
**Option 3: Link to Any MediaCMS Page**
- Add activity → External tool → MediaCMS
- In **"Custom parameters"**, add:
```
redirect_path=/featured
```
- Supported paths:
- `/featured` - Featured videos page
- `/latest` - Latest videos
- `/search/?q=keyword` - Search results
- `/category/category-name` - Specific category
- `/user/username` - User's profile
- Any other MediaCMS page path
**Option 4: Embed Specific Media via Deep Linking (Interactive)**
⚠️ **Prerequisite:** Ensure "Supports Deep Linking (Content-Item Message)" is enabled in the External Tool configuration (see section 3.A above)
When adding the activity to your course:
1. Add activity → External tool → MediaCMS
2. In the activity settings, enable **"Supports Deep Linking"** checkbox (may be under "Tool settings" or "Privacy" section)
3. Click **"Select content"** button → This launches the MediaCMS media browser
4. Browse and select media from MediaCMS (you can select multiple)
5. Click **"Add to course"** → Returns to Moodle with selected media configured
6. The activity will be automatically configured with the selected media's title and embed URL
7. Students clicking this activity will go directly to the selected media
### D. Custom Parameters - Complete Examples
**Example 1: Link to a specific video titled "Lecture 1 - Introduction"**
```
Activity Name: Lecture 1 - Introduction
Custom Parameters:
media_friendly_token=a1b2c3d4e5
```
**Example 2: Link to course-specific videos**
```
Activity Name: Course Videos
Custom Parameters:
redirect_path=/category/biology101
```
**Example 3: Link to search results for "genetics"**
```
Activity Name: Genetics Videos
Custom Parameters:
redirect_path=/search/?q=genetics
```
**Example 4: Link to featured content**
```
Activity Name: Featured Videos
Custom Parameters:
redirect_path=/featured
```
**Where to find Custom Parameters in Moodle:**
1. When creating/editing the External Tool activity
2. Expand **"Privacy"** section, or look for **"Additional Settings"**
3. Find the **"Custom parameters"** text field
4. Enter one parameter per line in the format: `key=value`
## 4. Testing Checklist
- [ ] HTTPS is working on MediaCMS
- [ ] `USE_LTI = True` in local_settings.py
- [ ] LTIPlatform configured in Django admin
- [ ] External tool registered in LMS
- [ ] Launch from LMS creates new user in MediaCMS
- [ ] Course is mapped to MediaCMS category
- [ ] Users are added to RBAC group with correct roles
- [ ] Media from course category is visible to course members
- [ ] Public media is accessible
- [ ] Private media from other courses is not accessible
## 5. Default Role Mappings
The system automatically maps LMS roles to MediaCMS:
- **Instructor/Teacher** → advancedUser (global) + manager (course group)
- **Student/Learner** → user (global) + member (course group)
- **Teaching Assistant** → user (global) + contributor (course group)
- **Administrator** → manager (global) + manager (course group)
You can customize these in Django admin under **LTI Role Mappings**.
## 6. User Creation and Authentication
### User Creation via LTI
When a user launches MediaCMS from your LMS for the first time, a MediaCMS account is automatically created with:
- **Username:** Generated from email (preferred) or name, or a unique ID if neither is available
- **Email:** From LTI claim (if shared by LMS)
- **Name:** From LTI given_name/family_name claims (if shared by LMS)
- **Roles:** Mapped from LTI roles to MediaCMS permissions
- **Course membership:** Automatically added to the RBAC group for the course
### Privacy Settings Are Critical
⚠️ **For proper user accounts, you must configure the LTI tool's privacy settings in Moodle:**
1. Edit the External Tool configuration in Moodle
2. Go to the **Privacy** section
3. Set **"Share launcher's name with tool"** to **Always**
4. Set **"Share launcher's email with tool"** to **Always**
Without these settings:
- Users will not have proper names in MediaCMS
- Users will not have email addresses
- Usernames will be generic hashes (e.g., `lti_user_abc123def`)
### Authentication
Users created through LTI integration do **not** have a password set. They can only access MediaCMS through LTI launches from your LMS. This is intentional for security.
If you need a user to have both LTI access and direct login capability, manually set a password using:
```bash
python manage.py changepassword <username>
```
## Need Help?
If you encounter issues, check:
- `/admin/lti/ltilaunchlog/` for launch attempt logs
- Django logs for detailed error messages
- Ensure HTTPS is properly configured (required for iframe cookies)
- Verify all URLs are correct and accessible
- Check that the Client ID and Deployment ID match between MediaCMS and your LMS

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
.PHONY: admin-shell build-frontend
admin-shell:
@container_id=$$(docker compose ps -q web); \
if [ -z "$$container_id" ]; then \
echo "Web container not found"; \
exit 1; \
else \
docker exec -it $$container_id /bin/bash; \
fi
build-frontend:
docker compose -f docker-compose-dev.yaml exec frontend npm run dist
cp -r frontend/dist/static/* static/
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

@@ -1,15 +1,12 @@
# MediaCMS
[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/python/g/mediacms-io/mediacms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mediacms-io/mediacms/context:python)
[![Code Quality: Cpp](https://img.shields.io/lgtm/grade/javascript/g/mediacms-io/mediacms.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mediacms-io/mediacms/context:javascript)
<br/>
[![GitHub license](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
[![Releases](https://img.shields.io/github/v/release/mediacms-io/mediacms?color=green)](https://github.com/mediacms-io/mediacms/releases/)
[![DockerHub](https://img.shields.io/docker/pulls/mediacms/mediacms)](https://hub.docker.com/repository/docker/mediacms/mediacms/)
[![DockerHub](https://img.shields.io/docker/pulls/mediacms/mediacms)](https://hub.docker.com/r/mediacms/mediacms)
MediaCMS is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media. It can be used to build a small to medium video and media portal within minutes.
MediaCMS is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media. It can be used to build a small to medium video and media portal within minutes.
It is built mostly using the modern stack Django + React and includes a REST API.
@@ -26,11 +23,15 @@ A demo is available at https://demo.mediacms.io
## Features
- **Complete control over your data**: host it yourself!
- **Support for multiple publishing workflows**: public, private, unlisted and custom
- **Modern technologies**: Django/Python/Celery, React.
- **Support for multiple publishing workflows**: public, private, unlisted and custom
- **Role-Based Access Control (RBAC)**: create RBAC categories and connect users to groups with view/edit access on their media
- **Automatic transcription**: through integration with Whisper running locally
- **Multiple media types support**: video, audio, image, pdf
- **Multiple media classification options**: categories, tags and custom
- **Multiple media sharing options**: social media share, videos embed code generation
- **Video Trimmer**: trim video, replace, save as new or create segments
- **SAML support**: with ability to add mappings to system roles and groups
- **Easy media searching**: enriched with live search functionality
- **Playlists for audio and video content**: create playlists, add and reorder content
- **Responsive design**: including light and dark themes
@@ -38,83 +39,83 @@ A demo is available at https://demo.mediacms.io
- **Configurable actions**: allow download, add comments, add likes, dislikes, report media
- **Configuration options**: change logos, fonts, styling, add more pages
- **Enhanced video player**: customized video.js player with multiple resolution and playback speed options
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Multiple transcoding profiles**: sane defaults for multiple dimensions (144p, 240p, 360p, 480p, 720p, 1080p) and multiple profiles (h264, h265, vp9)
- **Adaptive video streaming**: possible through HLS protocol
- **Subtitles/CC**: support for multilingual subtitle files
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
- **Chunked file uploads**: for pausable/resumable upload of content
- **REST API**: Documented through Swagger
- **Translation**: Most of the CMS is translated to a number of languages
## 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.
- **Universities, 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.
- **Organization sensitive content.** In cases where content is sensitive and cannot be uploaded to external sites.
- **Build a great community.** MediaCMS can be customized (URLs, logos, fonts, aesthetics) so that you create a highly customized video portal for your community!
- **Personal portal.** Organize, categorize and host your content the way you prefer.
## Philosophy
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
We believe there's a need for quality open source web applications that can be used to build community portals and support collaboration.
We have three goals for MediaCMS: a) deliver all functionality one would expect from a modern system, b) allow for easy installation and maintenance, c) allow easy customization and addition of features.
## License
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
Copyright Markos Gogoulos and Yiannis Stergiou
MediaCMS is released under [GNU Affero General Public License v3.0 license](LICENSE.txt).
Copyright Markos Gogoulos.
## Support and paid services
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. Checkout our [services page](https://mediacms.io/#services/) 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.
## Hardware dependencies
[![Deploy on Elestio](https://elest.io/images/logos/deploy-to-elestio-btn.png)](https://elest.io/open-source/mediacms)
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.
## 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.
In terms of disk space, think of what the needs will be. A general rule is to multiply by three the size of the expected uploaded videos (since the system keeps original versions, encoded versions plus HLS), so if you receive 1G of videos daily and maintain all of them, you should consider a 1T disk across a year (1G * 3 * 365).
## Releases
Visit [Releases Page](https://github.com/mediacms-io/mediacms/releases) for detailed Changelog
In order to support automatic transcriptions through Whisper, consider more CPUs.
## Installation / Maintanance
There are two ways to run MediaCMS, through Docker Compose and through installing it on a server via an automation script that installs and configures all needed services. Find the related pages:
* [Single Server](docs/Single_Server.md) page
* [Docker Compose](docs/Docker_Compose.md) page
## Configuration
Visit [Configuration](docs/Configuration.md) page.
- [Single Server](docs/admins_docs.md#2-server-installation) page
- [Docker Compose](docs/admins_docs.md#3-docker-installation) page
A complete guide can be found on the blog post [How to self-host and share your videos in 2021](https://medium.com/@MediaCMS.io/how-to-self-host-and-share-your-videos-in-2021-14067e3b291b).
## Documentation
* [Users documentation](docs/user_docs.md) page
* [Administrators documentation](docs/admins_docs.md) page
* [Developers documentation](docs/developers_docs.md) page
* [Configuration](docs/admins_docs.md#5-configuration) page
* [Transcoding](docs/transcoding.md) page
* [Developer Experience](docs/dev_exp.md) page
* [Media Permissions](docs/media_permissions.md) page
## Technology
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, uWSGI, React, Fine Uploader, video.js, FFMPEG, Bento4
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, Gunicorn, React, Fine Uploader, video.js, FFMPEG, Bento4
## Who is using it
- **Multiple Universities** for hosting educational videos
- **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.org/
## How to contribute
@@ -124,10 +125,12 @@ 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!
- Add functionality, work on a PR, fix an issue!
## Contact
info@mediacms.io
info@mediacms.io

View File

@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []

View File

@@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [

View File

@@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [

View File

View File

@@ -0,0 +1,86 @@
from django.apps import AppConfig
from django.conf import settings
from django.contrib import admin
class AdminCustomizationsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'admin_customizations'
def ready(self):
original_get_app_list = admin.AdminSite.get_app_list
def get_app_list(self, request, app_label=None):
"""Custom get_app_list"""
app_list = original_get_app_list(self, request, app_label)
# To see the list:
# print([a.get('app_label') for a in app_list])
email_model = None
rbac_group_model = None
identity_providers_user_log_model = None
identity_providers_login_option = None
auth_app = None
rbac_app = None
socialaccount_app = None
for app in app_list:
if app['app_label'] == 'users':
auth_app = app
elif app['app_label'] == 'account':
for model in app['models']:
if model['object_name'] == 'EmailAddress':
email_model = model
elif app['app_label'] == 'rbac':
if not getattr(settings, 'USE_RBAC', False):
continue
rbac_app = app
for model in app['models']:
if model['object_name'] == 'RBACGroup':
rbac_group_model = model
elif app['app_label'] == 'identity_providers':
if not getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
continue
models_to_check = list(app['models'])
for model in models_to_check:
if model['object_name'] == 'IdentityProviderUserLog':
identity_providers_user_log_model = model
if model['object_name'] == 'LoginOption':
identity_providers_login_option = model
elif app['app_label'] == 'socialaccount':
socialaccount_app = app
if email_model and auth_app:
auth_app['models'].append(email_model)
if rbac_group_model and rbac_app and auth_app:
auth_app['models'].append(rbac_group_model)
if identity_providers_login_option and socialaccount_app:
socialaccount_app['models'].append(identity_providers_login_option)
if identity_providers_user_log_model and socialaccount_app:
socialaccount_app['models'].append(identity_providers_user_log_model)
# 2. don't include the following apps
apps_to_hide = ['authtoken', 'auth', 'account', 'saml_auth', 'rbac']
if not getattr(settings, 'USE_RBAC', False):
apps_to_hide.append('rbac')
if not getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
apps_to_hide.append('socialaccount')
app_list = [app for app in app_list if app['app_label'] not in apps_to_hide]
# 3. change the ordering
app_order = {
'files': 1,
'users': 2,
'socialaccount': 3,
'rbac': 5,
}
app_list.sort(key=lambda x: app_order.get(x['app_label'], 999))
return app_list
admin.AdminSite.get_app_list = get_app_list

View File

View File

View File

10
cli-tool/README.md Normal file
View File

@@ -0,0 +1,10 @@
## MediaCMS CLI Tool
This is the CLI tool to interact with the API of your installation/instance of MediaCMS.
### How to configure and use the tools
- Make sure that you have all the required installations (`cli-tool/requirements.txt`)installed. To install it -
- Create a new virtualenv using any python virtualenv manager.
- Then activate the virtualenv and enter `pip install -r requirements.txt`.
- Create an .env file in this folder (`mediacms/cli-tool/`)
- Run the cli tool using the command `python cli.py login`. This will authenticate you and store necessary creds for further authentications.
- To check the credentials and necessary setup, run `python cli.py whoami`. This will show your details.

167
cli-tool/cli.py Normal file
View File

@@ -0,0 +1,167 @@
import json
import os
import click
import requests
from decouple import config
from rich import print
from rich.console import Console
from rich.table import Table
console = Console()
print("Welcome to the CLI Tool of [bold blue]MediaCMS![/bold blue]", ":thumbs_up:")
BASE_URL = 'https://demo.mediacms.io/api/v1'
AUTH_KEY = ''
USERNAME = ''
EMAIL = ''
def set_envs():
with open('.env', 'r') as file:
if not file.read(1):
print("Use the Login command to set your credential environment variables")
else:
global AUTH_KEY, USERNAME, EMAIL
AUTH_KEY = config('AUTH_KEY')
USERNAME = config('USERNAME')
EMAIL = config('EMAIL')
set_envs()
@click.group()
def apis():
"""A CLI wrapper for the MediaCMS API endpoints."""
@apis.command()
def login():
"""Login to your account."""
email = input('Enter your email address: ')
password = input('Enter your password: ')
data = {
"email": f"{email}",
"password": f"{password}",
}
response = requests.post(url=f'{BASE_URL}/login', data=data)
if response.status_code == 200:
username = json.loads(response.text)["username"]
with open(".env", "w") as file:
file.writelines(f'AUTH_KEY={json.loads(response.text)["token"]}\n')
file.writelines(f'EMAIL={json.loads(response.text)["email"]}\n')
file.writelines(f'USERNAME={json.loads(response.text)["username"]}\n')
print(f"Welcome to MediaCMS [bold blue]{username}[/bold blue]. Your auth creds have been suceesfully stored in the .env file", ":v:")
else:
print(f'Error: {"non_field_errors": ["User not found."]}')
@apis.command()
def upload_media():
"""Upload media to the server"""
headers = {'authorization': f'Token {AUTH_KEY}'}
path = input('Enter the location of the file or directory where multiple files are present: ')
if os.path.isdir(path):
for filename in os.listdir(path):
files = {}
abs = os.path.abspath(f"{path}/{filename}")
files['media_file'] = open(f'{abs}', 'rb')
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
if response.status_code == 201:
print(f"[bold blue]{filename}[/bold blue] successfully uploaded!")
else:
print(f'Error: {response.text}')
else:
files = {}
files['media_file'] = open(f'{os.path.abspath(path)}', 'rb')
response = requests.post(url=f'{BASE_URL}/media', headers=headers, files=files)
if response.status_code == 201:
print(f"[bold blue]{filename}[/bold blue] successfully uploaded!")
else:
print(f'Error: {response.text}')
@apis.command()
def my_media():
"""List all my media"""
headers = {'authorization': f'Token {AUTH_KEY}'}
response = requests.get(url=f'{BASE_URL}/media?author={USERNAME}', headers=headers)
if response.status_code == 200:
data_json = json.loads(response.text)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Name of the media")
table.add_column("Media Type")
table.add_column("State")
for data in data_json['results']:
table.add_row(data['title'], data['media_type'], data['state'])
console.print(table)
else:
print(f'Could not get the media: {response.text}')
@apis.command()
def whoami():
"""Shows the details of the authorized user"""
headers = {'authorization': f'Token {AUTH_KEY}'}
response = requests.get(url=f'{BASE_URL}/whoami', headers=headers)
for data, value in json.loads(response.text).items():
print(data, ' : ', value)
@apis.command()
def categories():
"""List all categories."""
response = requests.get(url=f'{BASE_URL}/categories')
if response.status_code == 200:
data_json = json.loads(response.text)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Category")
table.add_column("Description")
for data in data_json:
table.add_row(data['title'], data['description'])
console.print(table)
else:
print(f'Could not get the categories: {response.text}')
@apis.command()
def encodings():
"""List all encoding profiles"""
response = requests.get(url=f'{BASE_URL}/encode_profiles/')
if response.status_code == 200:
data_json = json.loads(response.text)
table = Table(show_header=True, header_style="bold magenta")
table.add_column("Name")
table.add_column("Extension")
table.add_column("Resolution")
table.add_column("Codec")
table.add_column("Description")
for data in data_json:
table.add_row(data['name'], data['extension'], str(data['resolution']), data['codec'], data['description'])
console.print(table)
else:
print(f'Could not get the encodings: {response.text}')
if __name__ == '__main__':
apis()

View File

@@ -0,0 +1,4 @@
click
python-decouple
requests
rich

10
cms/auth_backends.py Normal file
View File

@@ -0,0 +1,10 @@
from django.conf import settings
from django.contrib.auth.backends import ModelBackend
class ApprovalBackend(ModelBackend):
def user_can_authenticate(self, user):
can_authenticate = super().user_can_authenticate(user)
if can_authenticate and settings.USERS_NEEDS_TO_BE_APPROVED and not user.is_superuser:
return getattr(user, 'is_approved', False)
return can_authenticate

View File

@@ -3,6 +3,7 @@ from __future__ import absolute_import
import os
from celery import Celery
from django.conf import settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cms.settings")
app = Celery("cms")
@@ -14,5 +15,8 @@ app.conf.beat_schedule = app.conf.CELERY_BEAT_SCHEDULE
app.conf.broker_transport_options = {"visibility_timeout": 60 * 60 * 24} # 1 day
# http://docs.celeryproject.org/en/latest/getting-started/brokers/redis.html#redis-caveats
# setting this to settings.py file only is not respected. Setting here too
app.conf.task_always_eager = settings.CELERY_TASK_ALWAYS_EAGER
app.conf.worker_prefetch_multiplier = 1

View File

@@ -18,7 +18,6 @@ class FastPaginationWithoutCount(PageNumberPagination):
django_paginator_class = FasterDjangoPaginator
def get_paginated_response(self, data):
return Response(
OrderedDict(
[

58
cms/dev_settings.py Normal file
View File

@@ -0,0 +1,58 @@
# Development settings, used in docker-compose-dev.yaml
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
INSTALLED_APPS = [
"admin_customizations",
"django.contrib.auth",
"allauth",
"allauth.account",
"allauth.socialaccount",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"jazzmin",
"django.contrib.admin",
"django.contrib.sites",
"rest_framework",
"rest_framework.authtoken",
"imagekit",
"files.apps.FilesConfig",
"users.apps.UsersConfig",
"actions.apps.ActionsConfig",
"rbac.apps.RbacConfig",
"identity_providers.apps.IdentityProvidersConfig",
"lti.apps.LtiConfig",
"debug_toolbar",
"mptt",
"crispy_forms",
"crispy_bootstrap5",
"uploader.apps.UploaderConfig",
"djcelery_email",
"drf_yasg",
"allauth.socialaccount.providers.saml",
"saml_auth.apps.SamlAuthConfig",
"corsheaders",
"tinymce",
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
"django.middleware.locale.LocaleMiddleware",
'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',
"allauth.account.middleware.AccountMiddleware",
]
DEBUG = True
CORS_ORIGIN_ALLOW_ALL = True
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)
STATIC_ROOT = os.path.join(BASE_DIR, 'static_collected')

23
cms/middleware.py Normal file
View File

@@ -0,0 +1,23 @@
from django.conf import settings
from django.http import JsonResponse
from django.shortcuts import redirect
from django.urls import reverse
class ApprovalMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if settings.USERS_NEEDS_TO_BE_APPROVED and request.user.is_authenticated and not request.user.is_superuser and not getattr(request.user, 'is_approved', False):
allowed_paths = [
reverse('approval_required'),
reverse('account_logout'),
]
if request.path not in allowed_paths:
if request.path.startswith('/api/'):
return JsonResponse({'detail': 'User account not approved.'}, status=403)
return redirect('approval_required')
response = self.get_response(request)
return response

View File

@@ -1,14 +1,29 @@
from django.conf import settings
from rest_framework import permissions
from rest_framework.exceptions import PermissionDenied
from files.methods import is_mediacms_editor, is_mediacms_manager
from files.methods import (
is_mediacms_editor,
is_mediacms_manager,
user_allowed_to_upload,
)
class IsAuthorizedToAdd(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return user_allowed_to_upload(request)
if not user_allowed_to_upload(request):
raise PermissionDenied("You don't have permission to upload media, or have reached max number of media uploads.")
return True
class IsAuthorizedToAddComment(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return user_allowed_to_comment(request)
class IsUserOrManager(permissions.BasePermission):
@@ -48,21 +63,22 @@ class IsUserOrEditor(permissions.BasePermission):
return obj.user == request.user
def user_allowed_to_upload(request):
def user_allowed_to_comment(request):
"""Any custom logic for whether a user is allowed
to upload content lives here
to comment lives here
"""
if request.user.is_anonymous:
return False
if request.user.is_superuser:
return True
if settings.CAN_ADD_MEDIA == "all":
# Default is "all"
if not hasattr(settings, "CAN_COMMENT") or settings.CAN_COMMENT == "all":
return True
elif settings.CAN_ADD_MEDIA == "email_verified":
elif settings.CAN_COMMENT == "email_verified":
if request.user.email_is_verified:
return True
elif settings.CAN_ADD_MEDIA == "advancedUser":
elif settings.CAN_COMMENT == "advancedUser":
if request.user.advancedUser:
return True
return False

View File

@@ -1,19 +1,24 @@
import os
from celery.schedules import crontab
from django.utils.translation import gettext_lazy as _
DEBUG = False
# PORTAL NAME, this is the portal title and
# is also shown on several places as emails
PORTAL_NAME = "MediaCMS"
LANGUAGE_CODE = "en-us"
PORTAL_DESCRIPTION = ""
TIME_ZONE = "Europe/London"
# who can add media
# valid options include 'all', 'email_verified', 'advancedUser'
CAN_ADD_MEDIA = "all"
# who can comment
# valid options include 'all', 'email_verified', 'advancedUser'
CAN_COMMENT = "all"
# valid choices here are 'public', 'private', 'unlisted
PORTAL_WORKFLOW = "public"
@@ -86,26 +91,51 @@ MAX_MEDIA_PER_PLAYLIST = 70
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
MAX_CHARS_FOR_COMMENT = 10000 # so that it doesn't end up huge
TIMESTAMP_IN_TIMEBAR = False # shows timestamped comments in the timebar for videos
ALLOW_MENTION_IN_COMMENTS = False # allowing to mention other users with @ in the comments
# valid options: content, author
RELATED_MEDIA_STRATEGY = "content"
# Whether or not to generate a sitemap.xml listing the pages on the site (default: False)
GENERATE_SITEMAP = False
# Whether to include media count numbers on categories and tags listing pages
INCLUDE_LISTING_NUMBERS = True
USE_I18N = True
USE_L10N = True
USE_TZ = True
SITE_ID = 1
# these are the portal logos (dark and light)
# set new paths for svg or png if you want to override
# svg has priority over png, so if you want to use
# custom pngs and not svgs, remove the lines with svgs
# or set as empty strings
# example:
# PORTAL_LOGO_DARK_SVG = ""
# PORTAL_LOGO_LIGHT_SVG = ""
# place the files on static/images folder
PORTAL_LOGO_DARK_SVG = "/static/images/logo_dark.svg"
PORTAL_LOGO_DARK_PNG = "/static/images/logo_dark.png"
PORTAL_LOGO_LIGHT_SVG = "/static/images/logo_light.svg"
PORTAL_LOGO_LIGHT_PNG = "/static/images/logo_dark.png"
# paths to extra css files to be included, eg "/static/css/custom.css"
# place css inside static/css folder
EXTRA_CSS_PATHS = []
# protection agains anonymous users
# per ip address limit, for actions as like/dislike/report
TIME_TO_ACTION_ANONYMOUS = 10 * 60
# django-allauth settings
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_LOGIN_METHODS = {"username", "email"}
ACCOUNT_EMAIL_REQUIRED = True # new users need to specify email
ACCOUNT_EMAIL_VERIFICATION = "optional" # 'mandatory' 'none'
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
ACCOUNT_USERNAME_MIN_LENGTH = "4"
ACCOUNT_USERNAME_MIN_LENGTH = 4
ACCOUNT_ADAPTER = "users.adapter.MyAccountAdapter"
ACCOUNT_SIGNUP_FORM_CLASS = "users.forms.SignupForm"
ACCOUNT_USERNAME_VALIDATORS = "users.validators.custom_username_validators"
@@ -113,13 +143,19 @@ ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = False
ACCOUNT_USERNAME_REQUIRED = True
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5
# registration won't be open, might also consider to remove links for register
USERS_CAN_SELF_REGISTER = True
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = ["xxx.com", "emaildomainwhatever.com"]
# by default users do not need to be approved. If this is set to True, then new users
# will have to be approved before they can login successfully
USERS_NEEDS_TO_BE_APPROVED = False
# Comma separated list of domains: ["organization.com", "private.organization.com", "org2.com"]
# Empty list disables.
ALLOWED_DOMAINS_FOR_USER_REGISTRATION = []
# django rest settings
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
@@ -146,10 +182,13 @@ STATIC_ROOT = BASE_DIR + "/static/"
# where uploaded + encoded media are stored
MEDIA_ROOT = BASE_DIR + "/media_files/"
MEDIA_UPLOAD_DIR = os.path.join(MEDIA_ROOT, "original/")
MEDIA_ENCODING_DIR = os.path.join(MEDIA_ROOT, "encoded/")
THUMBNAIL_UPLOAD_DIR = os.path.join(MEDIA_UPLOAD_DIR, "thumbnails/")
SUBTITLES_UPLOAD_DIR = os.path.join(MEDIA_UPLOAD_DIR, "subtitles/")
# these used to be os.path.join(MEDIA_ROOT, "folder/") but update to
# Django 3.1.9 requires not absolute paths to be utilized...
MEDIA_UPLOAD_DIR = "original/"
MEDIA_ENCODING_DIR = "encoded/"
THUMBNAIL_UPLOAD_DIR = f"{MEDIA_UPLOAD_DIR}/thumbnails/"
SUBTITLES_UPLOAD_DIR = f"{MEDIA_UPLOAD_DIR}/subtitles/"
HLS_DIR = os.path.join(MEDIA_ROOT, "hls/")
FFMPEG_COMMAND = "ffmpeg" # this is the path
@@ -171,7 +210,7 @@ CHUNKIZE_VIDEO_DURATION = 60 * 5
VIDEO_CHUNKS_DURATION = 60 * 4
# always get these two, even if upscaling
MINIMUM_RESOLUTIONS_TO_ENCODE = [240, 360]
MINIMUM_RESOLUTIONS_TO_ENCODE = [144, 240]
# default settings for notifications
# not all of them are implemented
@@ -211,13 +250,13 @@ POST_UPLOAD_AUTHOR_MESSAGE_UNLISTED_NO_COMMENTARY = ""
# only in case where unlisted workflow is used and no commentary
# exists
CANNOT_ADD_MEDIA_MESSAGE = ""
CANNOT_ADD_MEDIA_MESSAGE = "User cannot add media, or maximum number of media uploads has been reached."
# mp4hls command, part of Bendo4
# mp4hls command, part of Bento4
MP4HLS_COMMAND = "/home/mediacms.io/mediacms/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bin/mp4hls"
# highly experimental, related with remote workers
ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
ADMIN_TOKEN = ""
# this is used by remote workers to push
# encodings once they are done
# USE_BASIC_HTTP = True
@@ -232,35 +271,6 @@ ADMIN_TOKEN = "c2b8e1838b6128asd333ddc5e24"
# uncomment the two lines related to htpasswd
CKEDITOR_CONFIGS = {
"default": {
"toolbar": "Custom",
"width": "100%",
"toolbar_Custom": [
["Styles"],
["Format"],
["Bold", "Italic", "Underline"],
["HorizontalRule"],
[
"NumberedList",
"BulletedList",
"-",
"Outdent",
"Indent",
"-",
"JustifyLeft",
"JustifyCenter",
"JustifyRight",
"JustifyBlock",
],
["Link", "Unlink"],
["Image"],
["RemoveFormat", "Source"],
],
}
}
AUTH_USER_MODEL = "users.User"
LOGIN_REDIRECT_URL = "/"
@@ -270,7 +280,7 @@ AUTHENTICATION_BACKENDS = (
)
INSTALLED_APPS = [
"django.contrib.admin",
"admin_customizations",
"django.contrib.auth",
"allauth",
"allauth.account",
@@ -279,6 +289,8 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"jazzmin",
"django.contrib.admin",
"django.contrib.sites",
"rest_framework",
"rest_framework.authtoken",
@@ -286,24 +298,32 @@ INSTALLED_APPS = [
"files.apps.FilesConfig",
"users.apps.UsersConfig",
"actions.apps.ActionsConfig",
"rbac.apps.RbacConfig",
"identity_providers.apps.IdentityProvidersConfig",
"lti.apps.LtiConfig",
"debug_toolbar",
"mptt",
"crispy_forms",
"crispy_bootstrap5",
"uploader.apps.UploaderConfig",
"djcelery_email",
"ckeditor",
"drf_yasg",
"allauth.socialaccount.providers.saml",
"saml_auth.apps.SamlAuthConfig",
"tinymce",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"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",
"allauth.account.middleware.AccountMiddleware",
]
ROOT_URLCONF = "cms.urls"
@@ -331,11 +351,15 @@ WSGI_APPLICATION = "cms.wsgi.application"
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
"OPTIONS": {
"user_attributes": ("username", "email", "first_name", "last_name"),
"max_similarity": 0.7,
},
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {
"min_length": 5,
"min_length": 7,
},
},
{
@@ -378,16 +402,7 @@ LOGGING = {
},
}
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mediacms",
"HOST": "127.0.0.1",
"PORT": "5432",
"USER": "mediacms",
"PASSWORD": "mediacms",
}
}
DATABASES = {"default": {"ENGINE": "django.db.backends.postgresql", "NAME": "mediacms", "HOST": "127.0.0.1", "PORT": "5432", "USER": "mediacms", "PASSWORD": "mediacms", "OPTIONS": {'pool': True}}}
REDIS_LOCATION = "redis://127.0.0.1:6379/1"
@@ -441,6 +456,152 @@ LOCAL_INSTALL = False
# it is placed here so it can be overrided on local_settings.py
GLOBAL_LOGIN_REQUIRED = False
# TODO: separate settings on production/development more properly, for now
# this should be ok
CELERY_TASK_ALWAYS_EAGER = False
if os.environ.get("TESTING"):
CELERY_TASK_ALWAYS_EAGER = True
# if True, only show original, don't perform any action on videos
DO_NOT_TRANSCODE_VIDEO = False
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
LANGUAGES = [
('ar', _('Arabic')),
('bn', _('Bengali')),
('da', _('Danish')),
('nl', _('Dutch')),
('en', _('English')),
('fr', _('French')),
('de', _('German')),
('hi', _('Hindi')),
('id', _('Indonesian')),
('it', _('Italian')),
('ja', _('Japanese')),
('ko', _('Korean')),
('pt', _('Portuguese')),
('ru', _('Russian')),
('zh-hans', _('Simplified Chinese')),
('sl', _('Slovenian')),
('zh-hant', _('Traditional Chinese')),
('es', _('Spanish')),
('tr', _('Turkish')),
('el', _('Greek')),
('ur', _('Urdu')),
('he', _('Hebrew')),
]
LANGUAGE_CODE = 'en' # default language
TINYMCE_DEFAULT_CONFIG = {
"theme": "silver",
"height": 500,
"resize": "both",
"menubar": "file edit view insert format tools table help",
"menu": {
"format": {
"title": "Format",
"items": "blocks | bold italic underline strikethrough superscript subscript code | " "fontfamily fontsize align lineheight | " "forecolor backcolor removeformat",
},
},
"plugins": "advlist,autolink,autosave,lists,link,image,charmap,print,preview,anchor,"
"searchreplace,visualblocks,code,fullscreen,insertdatetime,media,table,paste,directionality,"
"code,help,wordcount,emoticons,file,image,media",
"toolbar": "undo redo | code preview | blocks | "
"bold italic | alignleft aligncenter "
"alignright alignjustify ltr rtl | bullist numlist outdent indent | "
"removeformat | restoredraft help | image media",
"branding": False, # remove branding
"promotion": False, # remove promotion
"body_class": "page-main-inner custom-page-wrapper", # class of the body element in tinymce
"block_formats": "Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3;",
"formats": { # customize h2 to always have emphasis-large class
"h2": {"block": "h2", "classes": "emphasis-large"},
},
"font_size_formats": "16px 18px 24px 32px",
"images_upload_url": "/tinymce/upload/",
"images_upload_handler": "tinymce.views.upload_image",
"automatic_uploads": True,
"file_picker_types": "image",
"paste_data_images": True,
"paste_as_text": False,
"paste_enable_default_filters": True,
"paste_word_valid_elements": "b,strong,i,em,h1,h2,h3,h4,h5,h6,p,br,a,ul,ol,li",
"paste_retain_style_properties": "all",
"paste_remove_styles": False,
"paste_merge_formats": True,
"sandbox_iframes": False,
}
SPRITE_NUM_SECS = 10
# number of seconds for sprite image.
# If you plan to change this, you must also follow the instructions on admins_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
# this calculation is redundant most probably, setting as an option
CALCULATE_MD5SUM = False
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
# allow option to override the default admin url
# keep the trailing slash
DJANGO_ADMIN_URL = "admin/"
# this are used around a number of places and will need to be well documented!!!
USE_SAML = False
USE_RBAC = False
USE_IDENTITY_PROVIDERS = False
USE_LTI = False # Enable LTI 1.3 integration
JAZZMIN_UI_TWEAKS = {"theme": "flatly"}
USE_ROUNDED_CORNERS = True
ALLOW_VIDEO_TRIMMER = True
ALLOW_CUSTOM_MEDIA_URLS = False
ALLOW_MEDIA_REPLACEMENT = False
ALLOW_ANONYMOUS_USER_LISTING = True
# Who can see the members page
# valid choices are all, editors, admins
CAN_SEE_MEMBERS_PAGE = "all"
# User search field setting
# valid choices are name_username, name_username_email
# this searches for users in the share media modal under my media
USER_SEARCH_FIELD = "name_username"
# Maximum number of media a user can upload
NUMBER_OF_MEDIA_USER_CAN_UPLOAD = 100
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
# If 'all' is in the list, no check is performed
ALLOWED_MEDIA_UPLOAD_TYPES = ["video", "audio", "image", "pdf"]
# transcription options
# the mediacms-full docker image needs to be used in order to be able to use transcription
# if you are using the mediacms-full image, change USE_WHISPER_TRANSCRIBE to True
USE_WHISPER_TRANSCRIBE = False
# by default all users can request a video to be transcribed. If you want to
# allow only editors, set this to False
USER_CAN_TRANSCRIBE_VIDEO = True
# Whisper transcribe options - https://github.com/openai/whisper
WHISPER_MODEL = "base"
# show a custom text in the sidebar footer, otherwise the default will be shown if this is empty
SIDEBAR_FOOTER_TEXT = ""
try:
# keep a local_settings.py file for local overrides
from .local_settings import * # noqa
@@ -451,21 +612,59 @@ except ImportError:
# local_settings not in use
pass
# Don't add new settings below that could be overridden in local_settings.py!!!
if "http" not in FRONTEND_HOST:
# FRONTEND_HOST needs a http:// preffix
FRONTEND_HOST = f"http://{FRONTEND_HOST}"
FRONTEND_HOST = f"http://{FRONTEND_HOST}" # noqa
if LOCAL_INSTALL:
SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https")
else:
SSL_FRONTEND_HOST = FRONTEND_HOST
# CSRF_COOKIE_SECURE = True
# SESSION_COOKIE_SECURE = True
PYSUBS_COMMAND = "pysubs2"
# 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
if GLOBAL_LOGIN_REQUIRED:
# this should go after the AuthenticationMiddleware middleware
MIDDLEWARE.insert(5, "login_required.middleware.LoginRequiredMiddleware")
LOGIN_REQUIRED_IGNORE_PATHS = [
r'/accounts/login/$',
r'/accounts/logout/$',
r'/accounts/signup/$',
]
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
MIDDLEWARE.insert(auth_index + 1, "django.contrib.auth.middleware.LoginRequiredMiddleware")
if USERS_NEEDS_TO_BE_APPROVED:
AUTHENTICATION_BACKENDS = (
'cms.auth_backends.ApprovalBackend',
'allauth.account.auth_backends.AuthenticationBackend',
)
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
MIDDLEWARE.insert(auth_index + 1, "cms.middleware.ApprovalMiddleware")
# LTI 1.3 Integration Settings
if USE_LTI:
# Session timeout for LTI launches (seconds)
LTI_SESSION_TIMEOUT = 3600 # 1 hour
# Cookie settings required for iframe embedding from LMS
# IMPORTANT: Requires HTTPS to be enabled
SESSION_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
# Use cached_db for reliability - stores in both cache AND database
# This prevents session loss during multiple simultaneous LTI launches

View File

@@ -1,5 +1,6 @@
import debug_toolbar
from django.conf.urls import include, url
from django.conf import settings
from django.conf.urls import include
from django.contrib import admin
from django.urls import path, re_path
from django.views.generic.base import TemplateView
@@ -13,19 +14,26 @@ schema_view = get_schema_view(
permission_classes=(AllowAny,),
)
# refactor seriously
urlpatterns = [
url(r"^__debug__/", include(debug_toolbar.urls)),
re_path(r"^__debug__/", include(debug_toolbar.urls)),
path(
"robots.txt",
TemplateView.as_view(template_name="robots.txt", content_type="text/plain"),
),
url(r"^", include("files.urls")),
url(r"^", include("users.urls")),
url(r"^accounts/", include("allauth.urls")),
url(r"^api-auth/", include("rest_framework.urls")),
path("admin/", admin.site.urls),
re_path(r"^", include("files.urls")),
re_path(r"^", include("users.urls")),
re_path(r"^accounts/", include("allauth.urls")),
re_path(r"^lti/", include("lti.urls")),
re_path(r"^api-auth/", include("rest_framework.urls")),
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
re_path(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path('docs/api/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
path("tinymce/", include("tinymce.urls")),
]
admin.site.site_header = "MediaCMS Admin"
admin.site.site_title = "MediaCMS"
admin.site.index_title = "Admin"

1
cms/version.py Normal file
View File

@@ -0,0 +1 @@
VERSION = "7.8124"

View File

@@ -1,5 +0,0 @@
from pytest_factoryboy import register
from tests.users.factories import UserFactory
register(UserFactory)

75
deic_setup_notes.md Normal file
View File

@@ -0,0 +1,75 @@
# MediaCMS: Document Changes for DEIC
## Configuration Changes
The following changes are required in `deploy/docker/local_settings.py`:
```python
# default workflow
PORTAL_WORKFLOW = 'private'
# Authentication Settings
# these two are necessary so that users cannot register through system accounts. They can only register through identity providers
REGISTER_ALLOWED = False
USERS_CAN_SELF_REGISTER = False
USE_RBAC = True
USE_SAML = True
USE_IDENTITY_PROVIDERS = True
# Proxy and SSL Settings
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
# SAML Configuration
SOCIALACCOUNT_ADAPTER = 'saml_auth.adapter.SAMLAccountAdapter'
ACCOUNT_USERNAME_VALIDATORS = "users.validators.less_restrictive_username_validators"
SOCIALACCOUNT_PROVIDERS = {
"saml": {
"provider_class": "saml_auth.custom.provider.CustomSAMLProvider",
}
}
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_EMAIL_REQUIRED = False
# if set to strict, user is created with the email from the saml provider without
# checking if the email is already on the system
# however if this is ommited, and user tries to login with an email that already exists on
# the system, then they get to the ugly form where it suggests they add a username/email/name
ACCOUNT_PREVENT_ENUMERATION = 'strict'
```
## SAML Configuration Steps
### Step 1: Add SAML Identity Provider
1. Navigate to Admin panel
2. Select "Identity Provider"
3. Configure as follows:
- **Provider**: saml # ensure this is set with lower case!
- **Provider ID**: `wayf.wayf.dk`
- **IDP Config Name**: `Deic` (or preferred name)
- **Client ID**: `wayf_dk` (important: defines the URL, e.g., `https://deic.mediacms.io/accounts/saml/wayf_dk`)
- **Site**: Set the default one
### Step 2: Add SAML Configuration
Can be set through the SAML Configurations tab:
1. **IDP ID**: Must be a URL, e.g., `https://wayf.wayf.dk`
2. **IDP Certificate**: x509cert from your SAML provider
3. **SSO URL**: `https://wayf.wayf.dk/saml2/idp/SSOService2.php`
4. **SLO URL**: `https://wayf.wayf.dk/saml2/idp/SingleLogoutService.php`
5. **SP Metadata URL**: The metadata URL set for the SP, e.g., `https://deic.mediacms.io/saml/metadata`. This should point to the URL of the SP and is autogenerated
### Step 3: Set the other Options
1. **Email Settings**:
- `verified_email`: When enabled, emails from SAML responses will be marked as verified
- `Remove from groups`: When enabled, user is removed from a group after login, if they have been removed from the group on the IDP
2. **Global Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in MediaCMS
3. **Group Role Mapping**: Maps the role returned by SAML (as set in the SAML Configuration tab) with the role in groups that user will be added
4. **Group mapping**: This creates groups associated with this IDP. Group ids as they come from SAML, associated with MediaCMS groups
5. **Category Mapping**: This maps a group id (from SAML response) with a category in MediaCMS

View File

@@ -7,6 +7,7 @@ ln -sf /dev/stdout /var/log/nginx/mediacms.io.access.log && ln -sf /dev/stderr /
cp /home/mediacms.io/mediacms/deploy/docker/local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
mkdir -p /home/mediacms.io/mediacms/{logs,media_files/hls}
touch /home/mediacms.io/mediacms/logs/debug.log
@@ -28,7 +29,9 @@ 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
# Exclude package-lock.json files that may not exist or be removed during frontend setup
find /home/mediacms.io/mediacms ! \( -path "*.git*" -o -name "package-lock.json" \) -exec chown www-data:$TARGET_GID {} + 2>/dev/null || true
chmod +x /home/mediacms.io/mediacms/deploy/docker/start.sh /home/mediacms.io/mediacms/deploy/docker/prestart.sh

View File

@@ -1,17 +1,19 @@
FRONTEND_HOST = 'http://localhost'
PORTAL_NAME = 'MediaCMS'
SECRET_KEY = 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2'
POSTGRES_HOST = 'db'
REDIS_LOCATION = "redis://redis:6379/1"
import os
FRONTEND_HOST = os.getenv('FRONTEND_HOST', 'http://localhost')
PORTAL_NAME = os.getenv('PORTAL_NAME', 'MediaCMS')
SECRET_KEY = os.getenv('SECRET_KEY', 'ma!s3^b-cw!f#7s6s0m3*jx77a@riw(7701**(r=ww%w!2+yk2')
REDIS_LOCATION = os.getenv('REDIS_LOCATION', 'redis://redis:6379/1')
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mediacms",
"HOST": POSTGRES_HOST,
"PORT": "5432",
"USER": "mediacms",
"PASSWORD": "mediacms",
"NAME": os.getenv('POSTGRES_NAME', 'mediacms'),
"HOST": os.getenv('POSTGRES_HOST', 'db'),
"PORT": os.getenv('POSTGRES_PORT', '5432'),
"USER": os.getenv('POSTGRES_USER', 'mediacms'),
"PASSWORD": os.getenv('POSTGRES_PASSWORD', 'mediacms'),
"OPTIONS": {'pool': True},
}
}
@@ -31,4 +33,4 @@ CELERY_RESULT_BACKEND = BROKER_URL
MP4HLS_COMMAND = "/home/mediacms.io/bento4/bin/mp4hls"
DEBUG = False
DEBUG = os.getenv('DEBUG', 'False') == 'True'

View File

@@ -1,3 +1,9 @@
# Use existing X-Forwarded-Proto from reverse proxy if present, otherwise use $scheme
map $http_x_forwarded_proto $forwarded_proto {
default $http_x_forwarded_proto;
'' $scheme;
}
server {
listen 80 ;
@@ -16,6 +22,10 @@ server {
location /media {
alias /home/mediacms.io/mediacms/media_files ;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
location / {
@@ -24,7 +34,10 @@ server {
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
}
}

99
deploy/docker/policy.xml Normal file
View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policymap [
<!ELEMENT policymap (policy)*>
<!ATTLIST policymap xmlns CDATA #FIXED ''>
<!ELEMENT policy EMPTY>
<!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
]>
<!--
Configure ImageMagick policies.
Domains include system, delegate, coder, filter, path, or resource.
Rights include none, read, write, execute and all. Use | to combine them,
for example: "read | write" to permit read from, or write to, a path.
Use a glob expression as a pattern.
Suppose we do not want users to process MPEG video images:
<policy domain="delegate" rights="none" pattern="mpeg:decode" />
Here we do not want users reading images from HTTP:
<policy domain="coder" rights="none" pattern="HTTP" />
The /repository file system is restricted to read only. We use a glob
expression to match all paths that start with /repository:
<policy domain="path" rights="read" pattern="/repository/*" />
Lets prevent users from executing any image filters:
<policy domain="filter" rights="none" pattern="*" />
Any large image is cached to disk rather than memory:
<policy domain="resource" name="area" value="1GP"/>
Use the default system font unless overwridden by the application:
<policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
Define arguments for the memory, map, area, width, height and disk resources
with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
exceeds policy maximum so memory limit is 1GB).
Rules are processed in order. Here we want to restrict ImageMagick to only
read or write a small subset of proven web-safe image types:
<policy domain="delegate" rights="none" pattern="*" />
<policy domain="filter" rights="none" pattern="*" />
<policy domain="coder" rights="none" pattern="*" />
<policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
-->
<policymap>
<!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
<policy domain="resource" name="memory" value="1GiB"/>
<policy domain="resource" name="map" value="30GiB"/>
<policy domain="resource" name="width" value="16MP"/>
<policy domain="resource" name="height" value="16MP"/>
<!-- <policy domain="resource" name="list-length" value="128"/> -->
<policy domain="resource" name="area" value="40GP"/>
<policy domain="resource" name="disk" value="100GiB"/>
<!-- <policy domain="resource" name="file" value="768"/> -->
<!-- <policy domain="resource" name="thread" value="4"/> -->
<!-- <policy domain="resource" name="throttle" value="0"/> -->
<!-- <policy domain="resource" name="time" value="3600"/> -->
<!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
<!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
<!-- <policy domain="path" rights="none" pattern="@*" /> -->
<!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
<!-- <policy domain="cache" name="synchronize" value="True"/> -->
<!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/>
<!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
<!-- <policy domain="system" name="shred" value="2"/> -->
<!-- <policy domain="system" name="precision" value="6"/> -->
<!-- <policy domain="system" name="font" value="/path/to/font.ttf"/> -->
<!-- <policy domain="system" name="pixel-cache-memory" value="anonymous"/> -->
<!-- <policy domain="system" name="shred" value="2"/> -->
<!-- <policy domain="system" name="precision" value="6"/> -->
<!-- not needed due to the need to use explicitly by mvg: -->
<!-- <policy domain="delegate" rights="none" pattern="MVG" /> -->
<!-- use curl -->
<policy domain="delegate" rights="none" pattern="URL" />
<policy domain="delegate" rights="none" pattern="HTTPS" />
<policy domain="delegate" rights="none" pattern="HTTP" />
<!-- in order to avoid to get image with password text -->
<policy domain="path" rights="none" pattern="@*"/>
<!-- disable ghostscript format types -->
<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="PS2" />
<policy domain="coder" rights="none" pattern="PS3" />
<policy domain="coder" rights="none" pattern="EPS" />
<policy domain="coder" rights="none" pattern="PDF" />
<policy domain="coder" rights="none" pattern="XPS" />
</policymap>

View File

@@ -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"
@@ -17,7 +17,7 @@ if [ X"$ENABLE_MIGRATIONS" = X"yes" ]; then
# post_save, needs redis to succeed (ie. migrate depends on redis)
DJANGO_SUPERUSER_PASSWORD=$ADMIN_PASSWORD python manage.py createsuperuser \
--no-input \
--username=admin \
--username=$ADMIN_USER \
--email=$ADMIN_EMAIL \
--database=default || true
echo "Created admin user with password: $ADMIN_PASSWORD"
@@ -37,7 +37,6 @@ fi
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
cp deploy/docker/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
cp deploy/docker/nginx.conf /etc/nginx/
#### Supervisord Configurations #####
@@ -45,12 +44,12 @@ cp deploy/docker/nginx.conf /etc/nginx/
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
echo "Enabling uwsgi app server"
cp deploy/docker/supervisord/supervisord-uwsgi.conf /etc/supervisor/conf.d/supervisord-uwsgi.conf
echo "Enabling gunicorn app server"
cp deploy/docker/supervisord/supervisord-gunicorn.conf /etc/supervisor/conf.d/supervisord-gunicorn.conf
fi
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
echo "Enabling nginx as uwsgi app proxy and media server"
echo "Enabling nginx as gunicorn app proxy and media server"
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
fi
@@ -67,4 +66,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

View File

@@ -11,7 +11,7 @@ else
echo "There is no script $PRE_START_PATH"
fi
# Start Supervisor, with Nginx and uWSGI
# Start Supervisor, with Nginx and Gunicorn
echo "Starting server using supervisord..."
exec /usr/bin/supervisord

View File

@@ -0,0 +1,9 @@
[program:gunicorn]
command=/home/mediacms.io/bin/gunicorn cms.wsgi:application --workers=2 --threads=2 --worker-class=gthread --bind=127.0.0.1:9000 --user=www-data --group=www-data --timeout=120 --keep-alive=5 --max-requests=1000 --max-requests-jitter=50 --access-logfile=- --error-logfile=- --log-level=info --chdir=/home/mediacms.io/mediacms
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
priority=100
startinorder=true
startsecs=0

View File

@@ -1,9 +0,0 @@
[program:uwsgi]
command=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/docker/uwsgi.ini
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
priority=100
startinorder=true
startsecs=0

View File

@@ -1,23 +0,0 @@
[uwsgi]
chdir = /home/mediacms.io/mediacms/
virtualenv = /home/mediacms.io
module = cms.wsgi
uid=www-data
gid=www-data
processes = 2
threads = 2
master = true
socket = 127.0.0.1:9000
workers = 2
vacuum = true
hook-master-start = unix_signal:15 gracefully_kill_them_all
need-app = true
die-on-term = true

View File

@@ -1,16 +0,0 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

View File

@@ -1,24 +0,0 @@
[Unit]
Description=MediaCMS celery beat
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
Restart=always
RestartSec=10
Environment=APP_DIR="/home/mediacms.io/mediacms"
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
Environment=CELERY_APP="cms"
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/beat%n.pid"
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/beat%N.log"
Environment=CELERYD_LOG_LEVEL="INFO"
Environment=APP_DIR="/home/mediacms.io/mediacms"
ExecStart=/bin/sh -c '${CELERY_BIN} beat -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR}'
ExecStop=/bin/kill -s TERM $MAINPID
[Install]
WantedBy=multi-user.target

View File

@@ -1,31 +0,0 @@
[Unit]
Description=MediaCMS celery long queue
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
Restart=always
RestartSec=10
Environment=APP_DIR="/home/mediacms.io/mediacms"
Environment=CELERYD_NODES="long1"
Environment=CELERY_QUEUE="long_tasks"
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
Environment=CELERY_APP="cms"
Environment=CELERYD_MULTI="multi"
Environment=CELERYD_OPTS="-Ofair --prefetch-multiplier=1"
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
Environment=CELERYD_LOG_LEVEL="INFO"
Environment=APP_DIR="/home/mediacms.io/mediacms"
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
[Install]
WantedBy=multi-user.target

View File

@@ -1,41 +0,0 @@
[Unit]
Description=MediaCMS celery short queue
After=network.target
[Service]
Type=forking
User=www-data
Group=www-data
Restart=always
RestartSec=10
Environment=APP_DIR="/home/mediacms.io/mediacms"
Environment=CELERYD_NODES="short1 short2"
Environment=CELERY_QUEUE="short_tasks"
# Absolute or relative path to the 'celery' command:
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
# App instance to use
# comment out this line if you don't use an app
Environment=CELERY_APP="cms"
# or fully qualified:
#CELERY_APP="proj.tasks:app"
# How to call manage.py
Environment=CELERYD_MULTI="multi"
# Extra command-line arguments to the worker
Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
# - %n will be replaced with the first part of the nodename.
# - %I will be replaced with the current child process index
# and is important when using the prefork pool to avoid race conditions.
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
Environment=CELERYD_LOG_LEVEL="INFO"
Environment=APP_DIR="/home/mediacms.io/mediacms"
ExecStart=/bin/sh -c '${CELERY_BIN} multi start ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
ExecStop=/bin/sh -c '${CELERY_BIN} multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
ExecReload=/bin/sh -c '${CELERY_BIN} multi restart ${CELERYD_NODES} -A ${CELERY_APP} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} --workdir=${APP_DIR} -Q ${CELERY_QUEUE}'
[Install]
WantedBy=multi-user.target

View File

@@ -1,13 +0,0 @@
-----BEGIN DH PARAMETERS-----
MIICCAKCAgEAo3MMiEY/fNbu+usIM0cDi6x8G3JBApv0Lswta4kiyedWT1WN51iQ
9zhOFpmcu6517f/fR9MUdyhVKHxxSqWQTcmTEFtz4P3VLTS/W1N5VbKE2VEMLpIi
wr350aGvV1Er0ujcp5n4O4h0I1tn4/fNyDe7+pHCdwM+hxe8hJ3T0/tKtad4fnIs
WHDjl4f7m7KuFfheiK7Efb8MsT64HDDAYXn+INjtDZrbE5XPw20BqyWkrf07FcPx
8o9GW50Ox7/FYq7jVMI/skEu0BRc8u6uUD9+UOuWUQpdeHeFcvLOgW53Z03XwWuX
RXosUKzBPuGtUDAaKD/HsGW6xmGr2W9yRmu27jKpfYLUb/eWbbnRJwCw04LdzPqv
jmtq02Gioo3lf5H5wYV9IYF6M8+q/slpbttsAcKERimD1273FBRt5VhSugkXWKjr
XDhoXu6vZgj8Opei38qPa8pI1RUFoXHFlCe6WpZQmU8efL8gAMrJr9jUIY8eea1n
u20t5B9ueb9JMjrNafcq6QkKhZLi6fRDDTUyeDvc0dN9R/3Yts97SXfdi1/lX7HS
Ht4zXd5hEkvjo8GcnjsfZpAC39QfHWkDaeUGEqsl3jXjVMfkvoVY51OuokPWZzrJ
M5+wyXNpfGbH67dPk7iHgN7VJvgX0SYscDPTtms50Vk7RwEzLeGuSHMCAQI=
-----END DH PARAMETERS-----

View File

@@ -1,84 +0,0 @@
server {
listen 80 ;
server_name localhost;
gzip on;
access_log /var/log/nginx/mediacms.io.access.log;
error_log /var/log/nginx/mediacms.io.error.log warn;
# # redirect to https if logged in
# if ($http_cookie ~* "sessionid") {
# rewrite ^/(.*)$ https://localhost/$1 permanent;
# }
# # redirect basic forms to https
# location ~ (login|login_form|register|mail_password_form)$ {
# rewrite ^/(.*)$ https://localhost/$1 permanent;
# }
location /static {
alias /home/mediacms.io/mediacms/static ;
}
location /media/original {
alias /home/mediacms.io/mediacms/media_files/original;
}
location /media {
alias /home/mediacms.io/mediacms/media_files ;
}
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
}
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate_key /etc/letsencrypt/live/localhost/privkey.pem;
ssl_certificate /etc/letsencrypt/live/localhost/fullchain.pem;
ssl_dhparam /etc/nginx/dhparams/dhparams.pem;
ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_ecdh_curve secp521r1:secp384r1;
ssl_prefer_server_ciphers on;
gzip on;
access_log /var/log/nginx/mediacms.io.access.log;
error_log /var/log/nginx/mediacms.io.error.log warn;
location /static {
alias /home/mediacms.io/mediacms/static ;
}
location /media/original {
alias /home/mediacms.io/mediacms/media_files/original;
#auth_basic "auth protected area";
#auth_basic_user_file /home/mediacms.io/mediacms/deploy/local_install/.htpasswd;
}
location /media {
alias /home/mediacms.io/mediacms/media_files ;
}
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
include /etc/nginx/sites-enabled/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
}
}

View File

@@ -1,58 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIFTjCCBDagAwIBAgISBNOUeDlerH9MkKmHLvZJeMYgMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDAzMTAxNzUxNDFaFw0y
MDA2MDgxNzUxNDFaMBYxFDASBgNVBAMTC21lZGlhY21zLmlvMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps5Jn18nW2tq/LYFDgQ1YZGLlpF/B2AAPvvH
3yuD+AcT4skKdZouVL/a5pXrptuYL5lthO9dlcja2tuO2ltYrb7Dp01dAIFaJE8O
DKd+Sv5wr8VWQZykqzMiMBgviml7TBvUHQjvCJg8UwmnN0XSUILCttd6u4qOzS7d
lKMMsKpYzLhElBT0rzhhsWulDiy6aAZbMV95bfR74nIWsBJacy6jx3jvxAuvCtkB
OVdOoVL6BPjDE3SNEk53bAZGIb5A9ri0O5jh/zBFT6tQSjUhAUTkmv9oZP547RnV
fDj+rdvCVk/fE+Jno36mcT183Qd/Ty3fWuqFoM5g/luhnfvWEwIDAQABo4ICYDCC
AlwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTd5EZBt74zu5XxT1uXQs6oM8qOuDAf
BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw
LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw
LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv
MBYGA1UdEQQPMA2CC21lZGlhY21zLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAXqdz+d9WwOe1Nkh90Eng
MnqRmgyEoRIShBh1loFxRVgAAAFwxcnL+AAABAMARzBFAiAb3yeBuW3j9MxcRc0T
icUBvEa/rH7Fv2eB0oQlnZ1exQIhAPf+CtTXmzxoeT/BBiivj4AmGDsq4xWhe/U6
BytYrKLeAHYAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFwxcnM
HAAABAMARzBFAiAuP5gKyyaT0LVXxwjYD9zhezvxf4Icx0P9pk75c5ao+AIhAK0+
fSJv+WTXciMT6gA1sk/tuCHuDFAuexSA/6TcRXcVMA0GCSqGSIb3DQEBCwUAA4IB
AQCPCYBU4Q/ro2MUkjDPKGmeqdxQycS4R9WvKTG/nmoahKNg30bnLaDPUcpyMU2k
sPDemdZ7uTGLZ3ZrlIva8DbrnJmrTPf9BMwaM6j+ZV/QhxvKZVIWkLkZrwiVI57X
Ba+rs5IEB4oWJ0EBaeIrzeKG5zLMkRcIdE4Hlhuwu3zGG56c+wmAPuvpIDlYoO6o
W22xRdxoTIHBvkzwonpVYUaRcaIw+48xnllxh1dHO+X69DT45wlF4tKveOUi+L50
4GWJ8Vjv7Fot/WNHEM4Mnmw0jHj9TPkIZKnPNRMdHmJ5CF/FJFDiptOeuzbfohG+
mdvuInb8JDc0XBE99Gf/S4/y
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----

View File

@@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmzkmfXydba2r8
tgUOBDVhkYuWkX8HYAA++8ffK4P4BxPiyQp1mi5Uv9rmleum25gvmW2E712VyNra
247aW1itvsOnTV0AgVokTw4Mp35K/nCvxVZBnKSrMyIwGC+KaXtMG9QdCO8ImDxT
Cac3RdJQgsK213q7io7NLt2UowywqljMuESUFPSvOGGxa6UOLLpoBlsxX3lt9Hvi
chawElpzLqPHeO/EC68K2QE5V06hUvoE+MMTdI0STndsBkYhvkD2uLQ7mOH/MEVP
q1BKNSEBROSa/2hk/njtGdV8OP6t28JWT98T4mejfqZxPXzdB39PLd9a6oWgzmD+
W6Gd+9YTAgMBAAECggEADnEJuryYQbf5GUwBAAepP3tEZJLQNqk/HDTcRxwTXuPt
+tKBD1F79WZu40vTjSyx7l0QOFQo/BDZsd0Ubx89fD1p3xA5nxOT5FTb2IifzIpe
4zjokOGo+BGDQjq10vvy6tH1+VWOrGXRwzawvX5UCRhpFz9sptQGLQmDsZy0Oo9B
LtavYVUqsbyqRWlzaclHgbythegIACWkqcalOzOtx+l6TGBRjej+c7URcwYBfr7t
XTAzbP+vnpaJovZyZT1eekr0OLzMpnjx4HvRvzL+NxauRpn6KfabsTfZlk8nrs4I
UdSjeukj1Iz8rGQilHdN/4dVJ3KzrlHVkVTBSjmMUQKBgQDaVXZnhAScfdiKeZbO
rdUAWcnwfkDghtRuAmzHaRM/FhFBEoVhdSbBuu+OUyBnIw/Ra4o2ePuEBcKIUiQO
w2tnE1CY5PPAcjw+OCSpvzy5xxjaqaRbm9BJp3FTeEYGLXERnchPpHg/NpexuF22
QOJ+FrysPyNMxuQp47ZwO9WT3QKBgQDDlSGjq/eeWxemwf7ZqMVlRyqsdJsgnCew
DkC62IGiYCBDfeEmndN+vcA/uzJHYV4iXiqS3aYJCWGaZFMhdIhIn5MgULvO1j5G
u/MxuzaaNPz22FlNCWTLBw4T1HOOvyTL+nLtZDKJ/BHxgHCmur1kiGvvZWrcCthD
afLEmseqrwKBgBuLZKCymxJTHhp6NHhmndSpfzyD8RNibzJhw+90ZiUzV4HqIEGn
Ufhm6Qn/mrroRXqaIpm0saZ6Q4yHMF1cchRS73wahlXlE4yV8KopojOd1pjfhgi4
o5JnOXjaV5s36GfcjATgLvtqm8CkDc6MaQaXP75LSNzKysYuIDoQkmVRAoGAAghF
rja2Pv4BU+lGJarcSj4gEmSvy/nza5/qSka/qhlHnIvtUAJp1TJRkhf24MkBOmgy
Fw6YkBV53ynVt05HsEGAPOC54t9VDFUdpNGmMpoEWuhKnUNQuc9b9RbLEJup3TjA
Avl8kPR+lzzXbtQX7biBLp6mKp0uPB0YubRGCN8CgYA0JMxK0x38Q2x3AQVhOmZh
YubtIa0JqVJhvpweOCFnkq3ebBpLsWYwiLTn86vuD0jupe5M3sxtefjkJmAKd8xY
aBU7QWhjh1fX4mzmggnbjcrIFbkIHsxwMeg567U/4AGxOOUsv9QUn37mqycqRKEn
YfUyYNLM6F3MmQAOs2kaHw==
-----END PRIVATE KEY-----

View File

@@ -1,13 +0,0 @@
[Unit]
Description=MediaCMS uwsgi
[Service]
ExecStart=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/local_install/uwsgi.ini
ExecStop=/usr/bin/killall -9 uwsgi
RestartSec=3
#ExecRestart=killall -9 uwsgi; sleep 5; /home/sss/bin/uwsgi --ini /home/sss/wordgames/uwsgi.ini
Restart=always
[Install]
WantedBy=multi-user.target

View File

@@ -1,7 +0,0 @@
/home/mediacms.io/mediacms/logs/*.log {
weekly
missingok
rotate 7
compress
notifempty
}

View File

@@ -1,38 +0,0 @@
user www-data;
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 10240;
}
worker_rlimit_nofile 20000; #each connection needs a filehandle (or 2 if you are proxying)
http {
proxy_connect_timeout 75;
proxy_read_timeout 12000;
client_max_body_size 5800M;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 10;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" "$gzip_ratio"';
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

View File

@@ -1,27 +0,0 @@
[uwsgi]
chdir = /home/mediacms.io/mediacms/
virtualenv = /home/mediacms.io
module = cms.wsgi
uid=www-data
gid=www-data
processes = 2
threads = 2
master = true
socket = 127.0.0.1:9000
#socket = /home/mediacms.io/mediacms/deploy/uwsgi.sock
workers = 2
vacuum = true
logto = /home/mediacms.io/mediacms/logs/errorlog.txt
disable-logging = true

View File

@@ -1,16 +0,0 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

View File

@@ -0,0 +1,40 @@
#!/bin/bash
# This script builds the video editor package and deploys the frontend assets to the static directory.
# How to run: sh deploy/scripts/build_and_deploy.sh
# Exit on any error
set -e
echo "Starting build process..."
# Build video editor package
echo "Building video editor package..."
cd frontend-tools/video-editor
yarn build:django
cd ../../
# Build chapter editor package
echo "Building chapters editor package..."
cd frontend-tools/chapters-editor
yarn build:django
cd ../../
# Build video js package
echo "Building video js package..."
cd frontend-tools/video-js
yarn build:django
cd ../../
# Run npm build in the frontend container
echo "Building frontend assets..."
docker compose -f docker-compose/docker-compose-dev-updated.yaml exec frontend npm run dist
# Copy static assets to the static directory
echo "Copying static assets..."
cp -r frontend/dist/static/* static/
# Restart the web service
echo "Restarting web service..."
docker compose -f docker-compose/docker-compose-dev-updated.yaml restart web
echo "Build and deployment completed successfully!"

View File

@@ -1,8 +1,35 @@
version: "3"
services:
migrations:
build:
context: .
dockerfile: ./Dockerfile
target: base
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms-dev:latest
volumes:
- ./:/home/mediacms.io/mediacms/
command: "./deploy/docker/prestart.sh"
environment:
DEVELOPMENT_MODE: True
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: 'admin'
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
frontend:
image: node:14
image: node:20
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
working_dir: /home/mediacms.io/mediacms/frontend/
@@ -14,36 +41,18 @@ services:
depends_on:
- web
web:
build:
context: .
dockerfile: ./Dockerfile-dev
image: mediacms/mediacms-dev:latest
command: "python manage.py runserver 0.0.0.0:80"
environment:
DEVELOPMENT_MODE: True
ports:
- "80:80"
volumes:
- ./:/home/mediacms.io/mediacms/
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
selenium_hub:
container_name: selenium_hub
image: selenium/hub
ports:
- "4444:4444"
selenium_chrome:
container_name: selenium_chrome
image: selenium/node-chrome-debug
environment:
- HUB_PORT_4444_TCP_ADDR=selenium_hub
- HUB_PORT_4444_TCP_PORT=4444
ports:
- "5900:5900"
depends_on:
- selenium_hub
- migrations
db:
image: postgres
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
@@ -51,8 +60,9 @@ services:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mediacms"]
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
@@ -64,3 +74,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

5
docker-compose.full.yaml Normal file
View File

@@ -0,0 +1,5 @@
version: "3"
services:
celery_worker:
image: mediacms/mediacms:full

View File

@@ -11,6 +11,9 @@ services:
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
# ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
@@ -59,7 +62,7 @@ services:
depends_on:
- migrations
db:
image: postgres
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
@@ -67,8 +70,9 @@ services:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mediacms"]
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
@@ -77,6 +81,6 @@ services:
restart: always
healthcheck:
test: ["CMD", "redis-cli","ping"]
interval: 30s
timeout: 10s
interval: 10s
timeout: 5s
retries: 3

View File

@@ -0,0 +1,124 @@
name: mediacms-dev
services:
migrations:
platform: linux/amd64
build:
context: ..
dockerfile: Dockerfile
args:
- DEVELOPMENT_MODE=True
image: mediacms/mediacms:latest
volumes:
- ../:/home/mediacms.io/mediacms/
command: "/home/mediacms.io/mediacms/deploy/docker/prestart.sh"
environment:
DEVELOPMENT_MODE: True
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
ADMIN_PASSWORD: 'admin'
restart: on-failure
depends_on:
redis:
condition: service_healthy
db:
condition: service_healthy
frontend:
image: node:20
user: "root"
volumes:
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
- frontend_node_modules:/home/mediacms.io/mediacms/frontend/node_modules
- scripts_node_modules:/home/mediacms.io/mediacms/frontend/packages/scripts/node_modules
- npm_cache:/home/node/.npm
working_dir: /home/mediacms.io/mediacms/frontend/
command: >
bash -c "
echo 'Checking dependencies...' &&
if [ ! -f node_modules/.install-complete ]; then
echo 'First-time setup or dependencies changed, installing...' &&
npm install --legacy-peer-deps --cache /home/node/.npm &&
cd packages/scripts &&
npm install --legacy-peer-deps --cache /home/node/.npm &&
npm run build &&
cd ../.. &&
touch node_modules/.install-complete &&
echo 'Dependencies installed successfully'
else
echo 'Dependencies already installed, skipping installation...' &&
if [ ! -d packages/scripts/dist ]; then
echo 'Building scripts package...' &&
cd packages/scripts &&
npm run build &&
cd ../..
fi
fi &&
echo 'Starting development server...' &&
npm run start
"
env_file:
- ${PWD}/frontend/.env
ports:
- "8088:8088"
depends_on:
- web
restart: unless-stopped
web:
platform: linux/amd64
image: mediacms/mediacms:latest
command: "python manage.py runserver 0.0.0.0:80"
environment:
DEVELOPMENT_MODE: True
ports:
- "80:80"
volumes:
- ../:/home/mediacms.io/mediacms/
depends_on:
- migrations
db:
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
environment:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
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
redis:
image: "redis:alpine"
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3
celery_worker:
platform: linux/amd64
image: mediacms/mediacms:latest
deploy:
replicas: 1
volumes:
- ../:/home/mediacms.io/mediacms/
environment:
ENABLE_UWSGI: 'no'
ENABLE_NGINX: 'no'
ENABLE_CELERY_BEAT: 'no'
ENABLE_MIGRATIONS: 'no'
DEVELOPMENT_MODE: True
depends_on:
- web
volumes:
frontend_node_modules:
scripts_node_modules:
npm_cache:

View File

@@ -18,6 +18,9 @@ services:
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
@@ -65,7 +68,7 @@ services:
depends_on:
- migrations
db:
image: postgres
image: postgres:17.2-alpine
volumes:
- ../postgres_data/:/var/lib/postgresql/data/
restart: always
@@ -73,8 +76,9 @@ services:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mediacms"]
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

View File

@@ -38,6 +38,9 @@ services:
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_MIGRATIONS: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
VIRTUAL_HOST: localhost
depends_on:
- migrations
@@ -67,7 +70,7 @@ services:
depends_on:
- migrations
db:
image: postgres
image: postgres:17.2-alpine
volumes:
- ../postgres_data/:/var/lib/postgresql/data/
restart: always
@@ -75,8 +78,9 @@ services:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mediacms"]
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

View File

@@ -38,6 +38,9 @@ services:
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
@@ -87,7 +90,7 @@ services:
depends_on:
- migrations
db:
image: postgres
image: postgres:17.2-alpine
volumes:
- ../postgres_data:/var/lib/postgresql/data/
restart: always
@@ -95,8 +98,9 @@ services:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mediacms"]
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

View File

@@ -11,6 +11,9 @@ services:
ENABLE_CELERY_SHORT: 'no'
ENABLE_CELERY_LONG: 'no'
ENABLE_CELERY_BEAT: 'no'
ADMIN_USER: 'admin'
ADMIN_EMAIL: 'admin@localhost'
#ADMIN_PASSWORD: 'uncomment_and_set_password_here'
command: "./deploy/docker/prestart.sh"
restart: on-failure
depends_on:
@@ -63,7 +66,7 @@ services:
depends_on:
- migrations
db:
image: postgres
image: postgres:17.2-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
restart: always
@@ -71,8 +74,9 @@ services:
POSTGRES_USER: mediacms
POSTGRES_PASSWORD: mediacms
POSTGRES_DB: mediacms
TZ: Europe/London
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mediacms"]
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

View File

@@ -1,133 +0,0 @@
## How To Add A Static Page To The Sidebar
### 1. Create your html page in templates/cms/
e.g. duplicate and rename about.html
```
sudo cp templates/cms/about.html templates/cms/volunteer.html
```
### 2. Create your css file in static/css/
```
touch static/css/volunteer.css
```
### 3. In your html file, update block headermeta to reflect your new page
```
{% block headermeta %}
<meta property="og:title" content="Volunteer - {{PORTAL_NAME}}">
<meta property="og:type" content="website">
<meta property="og:description" content="">
<meta name="twitter:card" content="summary">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [{
"@type": "ListItem",
"position": 1,
"name": "{{PORTAL_NAME}}",
"item": {
"@type": "WebPage",
"@id": "{{FRONTEND_HOST}}"
}
},
{
"@type": "ListItem",
"position": 2,
"name": "Volunteer",
"item": {
"@type": "VolunteerPage",
"@id": "{{FRONTEND_HOST}}/volunteer"
}
}]
}
</script>
<link href="{% static "css/volunteer.css" %}" rel="stylesheet"/>
{% endblock headermeta %}
```
### 4. In your html file, update block innercontent to reflect your actual content
Write whatever you like.
### 5. In your css file, write matching styles for you html file.
Write whatever you like.
### 6. Add your view to files/views.py
```
def volunteer(request):
"""Volunteer view"""
context = {}
return render(request, "cms/volunteer.html", context)
```
### 7. Add your url pattern to files/urls.py
```
urlpatterns = [
url(r"^$", views.index),
url(r"^about", views.about, name="about"),
url(r"^volunteer", views.volunteer, name="volunteer"),
```
### 8. Add your page to the left sidebar
To add a link to your page as a menu item in the left sidebar,
add the following code after the last line in _commons.js
```
/* Checks that a given selector has loaded. */
const checkElement = async selector => {
while ( document.querySelector(selector) === null) {
await new Promise( resolve => requestAnimationFrame(resolve) )
}
return document.querySelector(selector);
};
/* Checks that sidebar nav menu has loaded, then adds menu item. */
checkElement('.nav-menu')
.then((element) => {
(function(){
var a = document.createElement('a');
a.href = "/volunteer";
a.title = "Volunteer";
var s = document.createElement('span');
s.className = "menu-item-icon";
var icon = document.createElement('i');
icon.className = "material-icons";
icon.setAttribute("data-icon", "people");
s.appendChild(icon);
a.appendChild(s);
var linkText = document.createTextNode("Volunteer");
var t = document.createElement('span');
t.appendChild(linkText);
a.appendChild(t);
var listItem = document.createElement('li');
listItem.className = "link-item";
listItem.appendChild(a);
//if signed out use 3rd nav-menu
var elem = document.querySelector(".nav-menu:nth-child(3) nav ul");
var loc = elem.innerText;
if (loc.includes("About")){
elem.insertBefore(listItem, elem.children[2]);
} else { //if signed in use 4th nav-menu
elem = document.querySelector(".nav-menu:nth-child(4) nav ul");
elem.insertBefore(listItem, elem.children[2]);
}
})();
});
```
### 9. Restart the mediacms web server
On docker:
```
sudo docker stop mediacms_web_1 && sudo docker start mediacms_web_1
```
Otherwise
```
sudo systemctl restart mediacms
```

View File

@@ -1,270 +0,0 @@
## Configuration
Several options are available on `cms/settings.py`, most of the things that are allowed or should be disallowed are described there.
It is advisable to override any of them by adding it to `local_settings.py` .
In case of a the single server installation, add to `cms/local_settings.py` .
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
Any change needs restart of MediaCMS in order to take effect.
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
```bash
#systemctl restart mediacms
```
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
```bash
#docker-compose restart web celery_worker celery_beat
```
### change portal logo
Set a new svg file for the white theme (`static/images/logo_dark.svg`) or the dark theme (`static/images/logo_light.svg`)
### set global portal title
set `PORTAL_NAME`, eg
```
PORTAL_NAME = 'my awesome portal'
```
### who can add media
By default `CAN_ADD_MEDIA = "all"` means that all registered users can add media. Other valid options are:
- **email_verified**, a user not only has to register an account but also verify the email (by clicking the link sent upon registration). Apparently email configuration need to work, otherise users won't receive emails.
- **advancedUser**, only users that are marked as advanced users can add media. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
### what is the portal workflow
The `PORTAL_WORKFLOW` variable specifies what happens to newly uploaded media, whether they appear on listings (as the index page, or search)
- **public** is the default option and means that a media can appear on listings. If media type is video, it will appear once at least a task that produces an encoded version of the file has finished succesfully. For other type of files, as image/audio they appear instantly
- **private** means that newly uploaded content is private - only users can see it or MediaCMS editors, managers and admins. Those can also set the status to public or unlisted
- **unlisted** means that items are unlisted. However if a user visits the url of an unlisted media, it will be shown (as opposed to private)
### show/hide the Sign in button
to show button:
```
LOGIN_ALLOWED = True
```
to hide button:
```
LOGIN_ALLOWED = False
```
### show/hide the Register button
to show button:
```
REGISTER_ALLOWED = True
```
to hide button:
```
REGISTER_ALLOWED = False
```
### show/hide the upload media button
To show:
```
UPLOAD_MEDIA_ALLOWED = True
```
To hide:
```
UPLOAD_MEDIA_ALLOWED = False
```
### show/hide the actions buttons (like/dislike/report)
Make changes (True/False) to any of the following:
```
- CAN_LIKE_MEDIA = True # whether the like media appears
- CAN_DISLIKE_MEDIA = True # whether the dislike media appears
- CAN_REPORT_MEDIA = True # whether the report media appears
- CAN_SHARE_MEDIA = True # whether the share media appears
```
### show/hide the download option on a media
Edit `templates/config/installation/features.html` and set
```
download: false
```
### automatically hide media upon being reported
set a low number for variable `REPORTED_TIMES_THRESHOLD`
eg
```
REPORTED_TIMES_THRESHOLD = 2
```
once the limit is reached, media goes to private state and an email is sent to admins
### set a custom message on the media upload page
this message will appear below the media drag and drop form
```
PRE_UPLOAD_MEDIA_MESSAGE = 'custom message'
```
### set email settings
Set correct settings per provider
```
DEFAULT_FROM_EMAIL = 'info@mediacms.io'
EMAIL_HOST_PASSWORD = 'xyz'
EMAIL_HOST_USER = 'info@mediacms.io'
EMAIL_USE_TLS = True
SERVER_EMAIL = DEFAULT_FROM_EMAIL
EMAIL_HOST = 'mediacms.io'
EMAIL_PORT = 587
ADMIN_EMAIL_LIST = ['info@mediacms.io']
```
### disallow user registrations from specific domains
set domains that are not valid for registration via this variable:
```
RESTRICTED_DOMAINS_FOR_USER_REGISTRATION = [
'xxx.com', 'emaildomainwhatever.com']
```
### require a review by MediaCMS editors/managers/admins
set value
```
MEDIA_IS_REVIEWED = False
```
any uploaded media now needs to be reviewed before it can appear to the listings.
MediaCMS editors/managers/admins can visit the media page and edit it, where they can see the option to mark media as reviewed. By default this is set to True, so all media don't require to be reviewed
### specify maximum number of media for a playlist
set a different threshold on variable `MAX_MEDIA_PER_PLAYLIST`
eg
```
MAX_MEDIA_PER_PLAYLIST = 14
```
### specify maximum size of a media that can be uploaded
change `UPLOAD_MAX_SIZE`.
default is 4GB
```
UPLOAD_MAX_SIZE = 800 * 1024 * 1000 * 5
```
### specify maximum size of comments
change `MAX_CHARS_FOR_COMMENT`
default:
```
MAX_CHARS_FOR_COMMENT = 10000
```
### how many files to upload in parallel
set a different threshold for `UPLOAD_MAX_FILES_NUMBER`
default:
```
UPLOAD_MAX_FILES_NUMBER = 100
```
### force users confirm their email upon registrations
default option for email confirmation is optional. Set this to mandatory in order to force users confirm their email before they can login
```
ACCOUNT_EMAIL_VERIFICATION = 'optional'
```
### rate limit account login attempts
after this number is reached
```
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 20
```
sets a timeout (in seconds)
```
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 5
```
### disallow user registration
set the following variable to False
```
USERS_CAN_SELF_REGISTER = True
```
### configure notifications
Global notifications that are implemented are controlled by the following options:
```
USERS_NOTIFICATIONS = {
'MEDIA_ADDED': True,
}
```
If you want to disable notification for new media, set to False
Admins also receive notifications on different events, set any of the following to False to disable
```
ADMINS_NOTIFICATIONS = {
'NEW_USER': True,
'MEDIA_ADDED': True,
'MEDIA_REPORTED': True,
}
```
- NEW_USER: a new user is added
- MEDIA_ADDED: a media is added
- MEDIA_REPORTED: the report for a media was hit
### Google Analytics
Checkout the instructions by alberto98fx on [Google Analytics](/docs/robots_and_analytics.md) page.

View File

@@ -1,59 +0,0 @@
# Docker Compose
## Installation
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
For Ubuntu 18/20 systems this is:
```bash
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```
Then run as root
```bash
git clone https://github.com/mediacms-io/mediacms
cd mediacms
```
The default option is to serve MediaCMS on all ips available of the server (including localhost).
If you want to explore more options (including setup of https with letsencrypt certificate) checkout the docs on the [Docker deployment](/docs/Docker_deployment.md) page for different docker-compose setups to use.
Run
```bash
docker-compose up
```
This will download all MediaCMS related Docker images and start all containers. Once it finishes, MediaCMS will be installed and available on http://localhost or http://ip
A user admin has been created with random password, you should be able to see it at the end of migrations container, eg
```
migrations_1 | Created admin user with password: gwg1clfkwf
```
or if you have set the ADMIN_PASSWORD variable on Dockerfile, that variable will be set as the admin user's password
## Update
Get latest MediaCMS image and stop/start containers
```bash
cd /path/to/mediacms/installation
docker pull mediacms/mediacms
docker-compose down
docker-compose up
```
## Configuration
Checkout the configuration docs on [Configuration](/docs/Configuration.md) page.
## Maintenance
Database is stored on ../postgres_data/ and media_files on media_files/

View File

@@ -1,53 +0,0 @@
# MediaCMS on Docker
The mediacms image is built to use supervisord as the main process, which manages one or more services required to run mediacms. We can toggle which services are run in a given container by setting the environment variables below to `yes` or `no`:
* ENABLE_UWSGI
* ENABLE_NGINX
* ENABLE_CELERY_BEAT
* ENABLE_CELERY_SHORT
* ENABLE_CELERY_LONG
* ENABLE_MIGRATIONS
By default, all these services are enabled, but in order to create a scaleable deployment, some of them can be disabled, splitting the service up into smaller services.
Also see the `Dockerfile` for other environment variables which you may wish to override. Application settings, eg. `FRONTEND_HOST` can also be overridden by updating the `deploy/docker/local_settings.py` file.
See example deployments in the sections below. These example deployments have been tested on `docker-compose version 1.27.4` running on `Docker version 19.03.13`
To run, update the configs above if necessary, build the image by running `docker-compose build`, then run `docker-compose run`
# Simple Deployment, accessed as http://localhost
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database.
The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
# Server with ssl certificate through letsencrypt service, accessed as https://my_domain.com
Before trying this out make sure the ip points to my_domain.com.
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
Now run docker-compose -f docker-compose-letsencrypt.yaml up, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
# Advanced Deployment, accessed as http://localhost:8000
Here we can run 1 mediacms_web instance, with the FRONTEND_HOST in `deploy/docker/local_settings.py` configured as http://localhost:8000. This is bootstrapped by a single migrations instance and supported by a single celery_beat instance and 1 or more celery_worker instances. Redis and postgres containers are also used for persistence. Clients can access the service on http://localhost:8000, on the docker host machine. This is similar to [this deployment](../docker-compose.yaml), with a `port` defined in FRONTEND_HOST.
# Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
Here we can use `jwilder/nginx-proxy` to reverse proxy to 1 or more instances of mediacms_web supported by other services as mentioned in the previous deployment. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://mediacms.io, nginx-proxy has port 80 exposed. Clients can access the service on http://mediacms.io (Assuming DNS or the hosts file is setup correctly to point to the IP of the nginx-proxy instance). This is similar to [this deployment](../docker-compose-http-proxy.yaml).
# Advanced Deployment, with reverse proxy, accessed as https://localhost
The reverse proxy (`jwilder/nginx-proxy`) can be configured to provide SSL termination using self-signed certificates, letsencrypt or CA signed certificates (see: https://hub.docker.com/r/jwilder/nginx-proxy or [LetsEncrypt Example](https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) ). In this case the FRONTEND_HOST should be set to https://mediacms.io. This is similar to [this deployment](../docker-compose-http-proxy.yaml).
# A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
The architecture below generalises all the deployment scenarios above, and provides a conceptual design for other deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple mediacms_web instances and celery_workers. For large deployments, managed postgres, redis and storage may be adopted.
![MediaCMS](images/architecture.png)

View File

@@ -1,38 +0,0 @@
# Single Server
## Installation
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions.
Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
Automated script - tested on Ubuntu 18, Ubuntu 20, and Debian Buster
```bash
mkdir /home/mediacms.io && cd /home/mediacms.io/
git clone https://github.com/mediacms-io/mediacms
cd /home/mediacms.io/mediacms/ && bash ./install.sh
```
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
## Update
If you've used the above way to install MediaCMS, update with the following:
```bash
cd /home/mediacms.io/mediacms # enter mediacms directory
source /home/mediacms.io/bin/activate # use virtualenv
git pull # update code
python manage.py migrate # run Django migrations
sudo systemctl restart mediacms celery_long celery_short # restart services
```
## Configuration
Checkout the configuration docs on [Configuration](/docs/Configuration.md) page.
## Maintenance
Database can be backed up with pg_dump and media_files on /home/mediacms.io/mediacms/media_files include original files and encoded/transcoded versions

View File

@@ -1,20 +0,0 @@
## User scenarios to test
## test video media + image
try uploading a video + image, make sure they get encoded well and check they appear on index/search/category/author page
try editing/setting metadata, confirm action is performed, also that are searchable
try adding custom poster, confirm it loads well on video page/listings
try specifying different thumbnail time, confirm an automatic screenshot is taken
## portal workflow
change workflow to unlisted, check they don't appear on index/search/category/author page
## users management
create an admin, a MediaCMS editor and MediaCMS manager. All should see edit/delete on a media and also comments, and action should work.
For users edit and delete, only MediaCMS manager and admin should see edit/delete and these actions should work.
## test subtitle
add language and test subtitling

File diff suppressed because it is too large Load Diff

89
docs/dev_exp.md Normal file
View File

@@ -0,0 +1,89 @@
# 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`
* gunicorn 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.
If Django breaks due to an error (eg SyntaxError, while editing the code), you might have to restart it
```
docker compose -f docker-compose-dev.yaml restart web
```
### 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
### Helper commands
There is ongoing effort to provide helper commands, check the Makefile for what it supports. Eg
Bash into the web container:
```
user@user:~/mediacms$ make admin-shell
root@ca8c1096726b:/home/mediacms.io/mediacms# ./manage.py shell
```
Build the frontend:
```
user@user:~/mediacms$ make build-frontend
docker compose -f docker-compose-dev.yaml exec frontend npm run dist
> mediacms-frontend@0.9.1 dist /home/mediacms.io/mediacms/frontend
> mediacms-scripts rimraf ./dist && mediacms-scripts build --config=./config/mediacms.config.js --env=dist
...
```

View File

@@ -1,48 +1,80 @@
# Developers documentation
## Table of contents
- [System architecture](#system-architecture)
- [API documentation](#api-documentation)
- [How to contribute](#how-to-contribute)
- [Working with Docker tips](#working-with-docker-tips)
- [How video is transcoded](#how-video-is-transcoded)
- [1. Welcome](#1-welcome)
- [2. System architecture](#2-system-architecture)
- [3. API documentation](#3-api-documentation)
- [4. How to contribute](#4-how-to-contribute)
- [5. Working with Docker tips](#5-working-with-docker-tips)
- [6. Working with the automated tests](#6-working-with-the-automated-tests)
- [7. How video is transcoded](#7-how-video-is-transcoded)
## How to contribute
## 1. Welcome
This page is created for MediaCMS developers and contains related information.
## 2. System architecture
to be written
## 3. API documentation
API is documented using Swagger - checkout ot http://your_installation/swagger - example https://demo.mediacms.io/swagger/
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
An example of working with Python requests library:
```
import requests
auth = ('user' ,'password')
upload_url = "https://domain/api/v1/media"
title = 'x title'
description = 'x description'
media_file = '/tmp/file.mp4'
requests.post(
url=upload_url,
files={'media_file': open(media_file,'rb')},
data={'title': title, 'description': description},
auth=auth
)
```
## 4. How to contribute
Before you send a PR, make sure your code is properly formatted. For that, use `pre-commit install` to install a pre-commit hook and run `pre-commit run --all` and fix everything before you commit. This pre-commit will check for your code lint everytime you commit a code.
Checkout the [Code of conduct page](../CODE_OF_CONDUCT.md) if you want to contribute to this repository
## System architecture
## API documentation
API is documented using Swagger - checkout ot http://your_installation/swagger - example https://demo.mediacms.io/swagger/
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
## Working with Docker tips
## 5. Working with Docker tips
To perform the Docker installation, follow instructions to install Docker + Docker compose (docs/Docker_Compose.md) and then build/start docker-compose-dev.yaml . This will run the frontend application on port 8088 on top of all other containers (including the Django web application on port 80)
```
docker-compose -f docker-compose-dev.yaml build
docker-compose -f docker-compose-dev.yaml up
docker compose -f docker-compose-dev.yaml build
docker compose -f docker-compose-dev.yaml up
```
An `admin` user is created during the installation process. Its attributes are defined in `docker-compose-dev.yaml`:
```
ADMIN_USER: 'admin'
ADMIN_PASSWORD: 'admin'
ADMIN_EMAIL: 'admin@localhost'
```
### Frontend application changes
Eg change `frontend/src/static/js/pages/HomePage.tsx` , dev application refreshes in a number of seconds (hot reloading) and I see the changes, once I'm happy I can run
```
docker-compose -f docker-compose-dev.yaml -T frontend npm run dist
docker compose -f docker-compose-dev.yaml exec -T frontend npm run dist
```
And then in order for the changes to be visible on the application while served through nginx,
And then in order for the changes to be visible on the application while served through nginx,
```
cp -r frontend/dist/static/* static/
```
POST calls: cannot be performed through the dev server, you have to make through the normal application (port 80) and then see changes on the dev application on port 8088.
POST calls: cannot be performed through the dev server, you have to make through the normal application (port 80) and then see changes on the dev application on port 8088.
Make sure the urls are set on `frontend/.env` if different than localhost
@@ -50,14 +82,15 @@ Media page: need to upload content through the main application (nginx/port 80),
There are some issues with CORS too to resolve, in order for some pages to function, eg the manage comments page
```http://localhost:8088/manage-media.html px manage_media
```
http://localhost:8088/manage-media.html manage_media
```
### Backend application changes
After I make changes to the django application (eg make a change on `files/forms.py`) in order to see the changes I have to restart the web container
```
docker-compose -f docker-compose-dev.yaml restart web
docker compose -f docker-compose-dev.yaml restart web
```
## How video is transcoded
@@ -80,4 +113,43 @@ there is also an experimental small service (not commited to the repo currently)
When the Encode object is marked as success and chunk=False, and thus is available for download/stream, there is a task that gets started and saves an HLS version of the file (1 mp4-->x number of small .ts chunks). This would be FILES_C
This mechanism allows for workers that have access on the same filesystem (either localhost, or through a shared network filesystem, eg NFS/EFS) to work on the same time and produce results.
This mechanism allows for workers that have access on the same filesystem (either localhost, or through a shared network filesystem, eg NFS/EFS) to work on the same time and produce results.
## 6. Working with the automated tests
This instructions assume that you're using the docker installation
1. start docker-compose
```
docker compose up
```
2. Install the requirements on `requirements-dev.txt ` on web container (we'll use the web container for this)
```
docker compose exec -T web pip install -r requirements-dev.txt
```
3. Now you can run the existing tests
```
docker compose exec --env TESTING=True -T web pytest
```
The `TESTING=True` is passed for Django to be aware this is a testing environment (so that it runs Celery tasks as functions for example and not as background tasks, since Celery is not started in the case of pytest)
4. You may try a single test, by specifying the path, for example
```
docker compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
```
5. You can also see the coverage
```
docker compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
```
and of course...you are very welcome to help us increase it ;)

BIN
docs/images/Demo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

BIN
docs/images/Demo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/images/Demo3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/images/Mention1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
docs/images/Mention2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
docs/images/Mention3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
docs/images/Mention4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

166
docs/media_permissions.md Normal file
View File

@@ -0,0 +1,166 @@
# Media Permissions in MediaCMS
This document explains the permission system in MediaCMS, which controls who can view, edit, and manage media files.
## Overview
MediaCMS provides a flexible permission system that allows fine-grained control over media access. The system supports:
1. **Basic permissions** - Public, private, and unlisted media
2. **User-specific permissions** - Direct permissions granted to specific users
3. **Role-Based Access Control (RBAC)** - Category-based permissions through group membership
## Media States
Every media file has a state that determines its basic visibility:
- **Public** - Visible to everyone
- **Private** - Only visible to the owner and users with explicit permissions
- **Unlisted** - Not listed in public listings but accessible via direct link
## User Roles
MediaCMS has several user roles that affect permissions:
- **Regular User** - Can upload and manage their own media
- **Advanced User** - Additional capabilities (configurable)
- **MediaCMS Editor** - Can edit and review content across the platform
- **MediaCMS Manager** - Full management capabilities
- **Admin** - Complete system access
## Direct Media Permissions
The `MediaPermission` model allows granting specific permissions to individual users:
### Permission Levels
- **Viewer** - Can view the media even if it's private
- **Editor** - Can view and edit the media's metadata
- **Owner** - Full control, including deletion
## Role-Based Access Control (RBAC)
When RBAC is enabled (`USE_RBAC` setting), permissions can be managed through categories and groups:
1. Categories can be marked as RBAC-controlled
2. Users are assigned to RBAC groups with specific roles
3. RBAC groups are associated with categories
4. Users inherit permissions to media in those categories based on their role
### RBAC Roles
- **Member** - Can view media in the category
- **Contributor** - Can view and edit media in the category
- **Manager** - Full control over media in the category
## Permission Checking Methods
The User model provides several methods to check permissions:
```python
# From users/models.py
def has_member_access_to_media(self, media):
# Check if user can view the media
# ...
def has_contributor_access_to_media(self, media):
# Check if user can edit the media
# ...
def has_owner_access_to_media(self, media):
# Check if user has full control over the media
# ...
```
## How Permissions Are Applied
When a user attempts to access media, the system checks permissions in this order:
1. Is the media public? If yes, allow access.
2. Is the user the owner of the media? If yes, allow full access.
3. Does the user have direct permissions through MediaPermission? If yes, grant the corresponding access level.
4. If RBAC is enabled, does the user have access through category membership? If yes, grant the corresponding access level.
5. If none of the above, deny access.
## Media Sharing
Users can share media with others by:
1. Making it public or unlisted
2. Granting direct permissions to specific users
3. Adding it to a category that's accessible to an RBAC group
## Implementation Details
### Media Listing
When listing media, the system filters based on permissions:
```python
# Simplified example from files/views/media.py
def _get_media_queryset(self, request, user=None):
# 1. Public media
listable_media = Media.objects.filter(listable=True)
if not request.user.is_authenticated:
return listable_media
# 2. User permissions for authenticated users
user_media = Media.objects.filter(permissions__user=request.user)
# 3. RBAC for authenticated users
if getattr(settings, 'USE_RBAC', False):
rbac_categories = request.user.get_rbac_categories_as_member()
rbac_media = Media.objects.filter(category__in=rbac_categories)
# Combine all accessible media
return listable_media.union(user_media, rbac_media)
```
### Permission Checking
The system uses helper methods to check permissions:
```python
# From users/models.py
def has_member_access_to_media(self, media):
# First check if user is the owner
if media.user == self:
return True
# Then check RBAC permissions
if getattr(settings, 'USE_RBAC', False):
rbac_groups = RBACGroup.objects.filter(
memberships__user=self,
memberships__role__in=["member", "contributor", "manager"],
categories__in=media.category.all()
).distinct()
if rbac_groups.exists():
return True
# Then check MediaShare permissions for any access
media_permission_exists = MediaPermission.objects.filter(
user=self,
media=media,
).exists()
return media_permission_exists
```
## Best Practices
1. **Default to Private** - Consider setting new uploads to private by default
2. **Use Categories** - Organize media into categories for easier permission management
3. **RBAC for Teams** - Use RBAC for team collaboration scenarios
4. **Direct Permissions for Exceptions** - Use direct permissions for one-off sharing
## Configuration
The permission system can be configured through several settings:
- `USE_RBAC` - Enable/disable Role-Based Access Control
## Conclusion
MediaCMS provides a flexible and powerful permission system that can accommodate various use cases, from simple personal media libraries to complex team collaboration scenarios with fine-grained access control.

View File

@@ -1,55 +0,0 @@
# Google Analytics
1. Create a file:
``` touch $DIR/mediacms/templates/tracking.html ```
2. Add the Gtag/Analytics script
3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one:
```
<head>
{% block head %}
<title>{% block headtitle %}{{PORTAL_NAME}}{% endblock headtitle %}</title>
{% include "common/head-meta.html" %}
{% block headermeta %}
<meta property="og:title" content="{{PORTAL_NAME}}">
<meta property="og:type" content="website">
{%endblock headermeta %}
{% block externallinks %}{% endblock externallinks %}
{% include "common/head-links.html" %}
{% block topimports %}{%endblock topimports %}
{% include "config/index.html" %}
{% endblock head %}
</head>
```
4. Add ``` {% include "tracking.html" %} ``` at the end inside the section ```<head>```
5. If you are using Docker and didn't mount the entire dir you need to bind a new volume:
```
web:
image: mediacms/mediacms:latest
restart: unless-stopped
ports:
- "80:80"
deploy:
replicas: 1
volumes:
- ./templates/root.html:/home/mediacms.io/mediacms/templates/root.html
- ./templates/tracking.html://home/mediacms.io/mediacms/templates/tracking.html
```

315
docs/saml_entraid_setup.md Normal file
View File

@@ -0,0 +1,315 @@
# Integrating Microsoft Entra ID (formerly Azure AD) with MediaCMS via SAML Authentication
This guide provides step-by-step instructions on how to configure Microsoft Entra ID as a SAML Identity Provider (IdP) for MediaCMS, an open-source content management system. The goal is to enable single sign-on (SSO) authentication for users in a secure and scalable way.
## Table of Contents
1. [Overview](#overview)
2. [Prerequisites](#prerequisites)
3. [Step 1: Configure MediaCMS for SAML](#step-1-configure-mediacms-for-saml)
4. [Step 2: Register MediaCMS as an Enterprise App in Entra ID](#step-2-register-mediacms-as-an-enterprise-app-in-entra-id)
5. [Step 3: Configure SAML Settings in Entra ID](#step-3-configure-saml-settings-in-entra-id)
6. [Step 4: Configure SAML Settings in MediaCMS](#step-4-configure-saml-settings-in-mediacms)
7. [Step 5: Allow Users or Groups to Log Into the Application](#step-5-allow-users-or-groups-to-log-into-the-application)
8. [Step 6: Test and Validate Login Flow](#step-6-test-and-validate-login-flow)
9. [Troubleshooting](#troubleshooting)
10. [Resources](#resources)
---
## Overview
MediaCMS supports SAML 2.0 authentication by acting as a Service Provider (SP). By integrating with Microsoft Entra ID, organizations can allow users to authenticate using their existing enterprise credentials.
In our particular deployment of MediaCMS, the application is hosted internally with no direct inbound access from the public Internet. As an internal company application, it was essential to integrate it with our existing authentication systems and provide a seamless single sign-on experience. This is where the SAML protocol shines.
One of the major advantages of SAML authentication is that all communication between the Identity Provider (IdP) — in this case, Microsoft Entra ID — and the Service Provider (SP) — MediaCMS — is brokered entirely by the end user's browser. The browser initiates the authentication flow, communicates securely with Microsofts login portal, receives the identity assertion, and then passes it back to the internal MediaCMS server.
This architecture enables the MediaCMS server to remain isolated from the Internet while still participating in a modern and seamless federated login experience.
Even though the deployment method outlined in this tutorial is for EntraID on an isolated MediaCMS server, the same steps and general information could be applied to another authentication SAML provider/identity provider on a non-isolated system.
> **Note**: This guide assumes you are running MediaCMS with Django backend and that the `django-allauth` library is enabled and configured.
---
## Prerequisites
Before beginning, ensure the following:
* You have administrator access to both MediaCMS and Microsoft Entra ID (Azure portal).
* MediaCMS is installed and accessible via HTTPS, with a valid SSL certificate.
* Your MediaCMS installation has SAML support enabled (via `django-allauth`).
* You have a dedicated domain or subdomain for MediaCMS (e.g., `https://<MyMediaCMS.MyDomain.com>`).
---
## Step 1: Configure MediaCMS for SAML
The first step in enabling SAML authentication is to modify the `local_settings.py` (for Docker: `./deploy/docker/local_settings.py`) file of your MediaCMS deployment. Add the following configuration block to enable SAML support, role-based access control (RBAC), and enforce secure communication settings:
```python
USE_RBAC = True
USE_SAML = True
USE_IDENTITY_PROVIDERS = True
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SOCIALACCOUNT_ADAPTER = 'saml_auth.adapter.SAMLAccountAdapter'
SOCIALACCOUNT_PROVIDERS = {
"saml": {
"provider_class": "saml_auth.custom.provider.CustomSAMLProvider",
}
}
```
These settings enable SAML authentication, configure MediaCMS to respect role-based access, and apply important headers and cookie policies for secure browser handling — all of which are necessary for the SAML flow to function properly.
> ⚠️ **Important**: After updating the `local_settings.py` file, you must restart your MediaCMS service (e.g., by rebooting the Docker container) in order for the changes to take effect. This step must be completed before proceeding to the next configuration stage.
---
## Step 2: Register MediaCMS as an Enterprise App in Entra ID
To begin the integration process on the Microsoft Entra ID (formerly Azure AD) side, follow the steps below to register MediaCMS as a new Enterprise Application.
### 1. Navigate to Enterprise Applications
* Log in to your [Azure Portal](https://portal.azure.com).
* Navigate to **Enterprise Applications**.
> *Note: This guide assumes you already have an existing Azure tenant and Entra ID configured with users and groups.*
### 2. Create a New Application
* Click the **+ New Application** button.
* On the next screen, choose **Create your own application**.
* Enter a name for the application (e.g., `MediaCMS`).
* Under "What are you looking to do with your application?", select **Integrate any other application you don't find in the gallery (Non-gallery)**.
* Click **Create**.
After a few moments, Azure will create the new application and redirect you to its configuration page.
---
## Step 3: Configure SAML Settings in Entra ID
### 1. Configure SAML-Based Single Sign-On
* From the application overview page, in the left-hand menu under **Manage**, click **Single sign-on**.
* You will be prompted to choose a sign-on method. Select **SAML**.
### 2. Choose a Client ID Name
Before filling out the SAML configuration, you must decide on a client ID name. This name will uniquely identify your SAML integration and appear in your login URL.
* Choose a name that is descriptive and easy to remember (e.g., `mediacms_entraid`).
* You will use this name in both MediaCMS and Entra ID configuration settings.
### 3. Fill Out Basic SAML Configuration
Now input the following values under the **Basic SAML Configuration** section:
| Field | Value |
| -------------------------- | --------------------------------------------------------------------- |
| **Identifier (Entity ID)** | `https://<MyMediaCMS.MyDomain.com>/saml/metadata/` |
| **Reply URL (ACS URL)** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/acs/` |
| **Sign-on URL** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/login/` |
| **Relay State (Optional)** | `https://<MyMediaCMS.MyDomain.com>/` |
| **Logout URL (Optional)** | `https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/sls/` |
> 🔐 Replace `<MyClientID>` with your own chosen client ID if different.
Once these fields are filled in, save your configuration.
Keep the Azure Enterprise single sign-on configuration window up, as we are now going to configure some of the details from this Azure page into our MediaCMS system.
---
## Step 4: Configure SAML Settings in MediaCMS
In MediaCMS, start by logging into the back-end administrative web page. You will now have new options under the left-hand menu bar.
### 1. Add Login Option
* Navigate to **Identity Providers → Login Options**.
* Click **Add Login Option**.
* Give the login option a title. This title can be anything you like but it will appear to the end-user when they select a method of logging in, so ensure the name is clear. (e.g., `EntraID-SSO`).
* Set the **Login URL** to the same Sign-on URL:
```
https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/login/
```
* Leave the ordering at `0` if you have no other authentication methods.
* Ensure the **Active** box is checked to make this an active login method.
* Click **Save** to continue.
### 2. Add ID Provider
* Navigate to **Identity Providers → ID Providers**.
* Click **Add ID Provider**.
Back in your Azure Enterprise application configuration window (at the bottom of the Single Sign-On configuration menu), find your application-specific details. They will look like the following example:
```
Example unique AppID: 123456ab-1234-12ab-ab12-abc123abc123
The unique AppID is automatically generated when you create the application.
-- Example URLs --
Login URL: https://login.microsoftonline.com/123456ab-1234-12ab-ab12-abc123abc123/saml2
Microsoft Entra Identifier: https://sts.windows.net/123456ab-1234-12ab-ab12-abc123abc123/
Logout URL: https://login.microsoftonline.com/123456ab-1234-12ab-ab12-abc123abc123/saml2
```
Back in MediaCMS's new ID Provider window, under the **General** tab:
* **Protocol**: `saml` (all lowercase)
* **Provider ID**: The Microsoft Entra Identifier (as shown above), the whole URL.
* **IDP Configuration Name**: Any unique name (e.g., `EntraID`)
* **Client ID**: The exact same client ID you used earlier when configuring EntraID (e.g., `mediacms_entraid`).
* **Sites**: Add all the sites you want this login to appear on (e.g., all of them)
Click **Save and Continue**, then go to the **SAML Configuration** tab.
On the **SAML Configuration** tab:
* **SSO URL**: Use the same Logon URL from EntraID example listed above.
* **SLO URL**: Use the Logout URL from EntraID example listed above.
* **SP Metadata URL**:
```
https://<MyMediaCMS.MyDomain.com>/saml/metadata/
```
* **IdP ID**: Use the same Microsoft Entra Identifier URL as listed above.
#### LDP Certificate
Back in Azure's Enterprise Application page (SAML certificates section), download the **Base64 Certificate**, open it in a text editor, and copy the contents into the **LDP Certificate** setting inside of MediaCMS.
### 3. Configure Identity Mappings
Map the identity attributes that Entra ID will provide to MediaCMS. Even though only UID is specified as mandatory, Entra ID will not work unless all of these details are filled in(YES, you must type NA in the fields; you cannot leave anything blank. You will get 500 errors if this is not done). You can use the exact settings below:
| Field | Value |
| -------------- | -------------------------------------------------------------------- |
| **Uid** | `http://schemas.microsoft.com/identity/claims/objectidentifier` |
| **Name** | `http://schemas.microsoft.com/identity/claims/displayname` |
| **Email** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` |
| **Groups** | `NA` |
| **First name** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname` |
| **Last name** | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname` |
| **User logo** | `NA` |
| **Role** | `NA` |
> Groups and Role can be changed or remapped inside the Azure Enterprise Application under **Attributes and Claims**.
Check the **Verified Email** box (since EntraID will verify the user for you). While setting up, you can enable **Save SAML Response Log** for troubleshooting purposes.
Finally, click **Save** to finish adding the new ID provider.
---
## Step 5: Allow Users or Groups to Log Into the Application
Back inside Azure AD, within your MediaCMS Enterprise Application, you must assign users or groups that are allowed to use the MediaCMS authentication sign-on.
### 1. Navigate to Users and Groups
* Open the Azure Portal and go to your **MediaCMS Enterprise Application**.
* In the left-hand **Manage** menu, click **Users and Groups**.
### 2. Assign Users or Groups
* Add individual users or groups of users who are allowed to use the EntraID authentication method with MediaCMS.
* In this example, the application was provided to all registered users inside of EntraID by using the special group **All Users**, which grants any registered user in the tenant access to MediaCMS.
> ⚠️ **Important**: Nested groups will not work. All users must be directly assigned to the group you are giving permission to. If a group contains another group, the users of the nested group will not inherit the permissions to use this application from the parent group.
---
## Step 6: Test and Validate Login Flow
At this point, you should go to your MediaCMS webpage and attempt to log in using the authentication method that you have just set up.
---
## Troubleshooting
If you're experiencing logon issues, it is helpful to first review the SAML authentication data directly.
1. Go to MediaCMS's login page. It should redirect you to Microsoft's login page.
2. Before completing the Microsoft authentication, open Firefox or Chrome Developer Tools (press **F12**) and navigate to the **Network** tab.
3. Enable **Persistent Logging**.
4. Complete the Microsoft authentication steps on your page (including two-factor authentication if enabled).
On the final step of the authentication (usually after entering a code and confirming "Stay signed in?"), you will see several POST requests going back to your MediaCMS server URL. Find the POST request that is going to your MediaCMS server's Assertion Consumer Service (ACS) URL, which will look like this:
```
https://<MyMediaCMS.MyDomain.com>/accounts/saml/<MyClientID>/acs/
```
Inside the request section of the Network tab, you will see a **Form Data** field labeled **SAMLResponse**, which contains a Base64-encoded XML string of your authenticated assertion from EntraID.
* Click into the data field of the SAML response so you can highlight and copy all of the Base64-encoded text.
* You can then take this Base64-encoded text to a tool like [CyberChef](https://gchq.github.io/CyberChef/) and use the **From Base64** decoder and **XML Beautify** to reveal the XML-formatted SAML response.
This decoded XML contains all the assertion and token details passed back to MediaCMS. You can use this information to troubleshoot any issues or misconfigurations that arise.
You can also confirm your MediaCMS server has the SAML authentication settings correct by opening a private browsing window and navigating to the following URL, which will output the current XML data that your MediaCMS server is configured with:
```
https://<MyMediaCMS.MyDomain.com>/saml/metadata/
```
You can use the returned XML data from this URL to confirm that MediaCMS is configured appropriately as expected and is providing the correct information to the identity provider.
### Infinite Redirect Loop
Another issue you might encounter is an **infinite redirect loop**. This can happen when global login is enforced and local user login is disabled.
**Symptoms:** The system continuously redirects between the homepage and the login URL.
**Root Cause:** With global login required and local login disabled, Django attempts to redirect users to the default local login page. Since that login method is unavailable, users are bounced back to the homepage, triggering the same redirect logic again — resulting in a loop.
**Solution:** Specify the correct SAML authentication URL in your local settings. For example:
* "Login Option" URL configured for EntraID in MediaCMS:
```
https://<MyDomainName>/accounts/saml/mediacms_entraid/login/
```
* Add the following line to `./deploy/docker/local_settings.py`:
```python
LOGIN_URL = "/accounts/saml/mediacms_entraid/login/"
```
This change ensures Django uses the proper SAML login route, breaking the redirect loop and allowing authentication via EntraID as intended.
> **Note:** The `LOGIN_URL` setting works because we are using the Django AllAuth module to perform the SAML authentication. If you review the AllAuth Django configuration settings, you will find that this is a setting, among other settings, that you can set inside of your local settings file that Django will pick up when using the AllAuth module. You can review the module documentation at the following URL for more details and additional settings that can be set through AllAuth via `local_settings.py`: [https://django-allauth.readthedocs.io/en/latest/account/configuration.html](https://django-allauth.readthedocs.io/en/latest/account/configuration.html)
---
## Resources
* [MediaCMS SAML Docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#24-identity-providers-setup)
* [Enable SAML single sign-on for an enterprise application](https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/add-application-portal-setup-sso)
* [Django AllAuth](https://django-allauth.readthedocs.io/en/latest/index.html)
---
*This documentation is a work-in-progress and will be updated as further steps are dictated or completed.*

50
docs/transcoding.md Normal file
View File

@@ -0,0 +1,50 @@
# Transcoding in MediaCMS
MediaCMS uses FFmpeg for transcoding media files. Most of the transcoding settings and configurations are defined in `files/helpers.py`.
## Configuration Options
Several transcoding parameters can be customized in `cms/settings.py`:
### FFmpeg Preset
The default FFmpeg preset is set to "medium". This setting controls the encoding speed and compression efficiency trade-off.
```python
# ffmpeg options
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
```
Available presets include:
- ultrafast
- superfast
- veryfast
- faster
- fast
- medium (default)
- slow
- slower
- veryslow
Faster presets result in larger file sizes for the same quality, while slower presets provide better compression but take longer to encode.
### Other Transcoding Settings
Additional transcoding settings in `settings.py` include:
- `FFMPEG_COMMAND`: Path to the FFmpeg executable
- `FFPROBE_COMMAND`: Path to the FFprobe executable
- `DO_NOT_TRANSCODE_VIDEO`: If set to True, only the original video is shown without transcoding
- `CHUNKIZE_VIDEO_DURATION`: For videos longer than this duration (in seconds), they get split into chunks and encoded independently
- `VIDEO_CHUNKS_DURATION`: Duration of each chunk (must be smaller than CHUNKIZE_VIDEO_DURATION)
- `MINIMUM_RESOLUTIONS_TO_ENCODE`: Always encode these resolutions, even if upscaling is required
## Advanced Configuration
For more advanced transcoding settings, you may need to modify the following in `files/helpers.py`:
- Video bitrates for different codecs and resolutions
- Audio encoders and bitrates
- CRF (Constant Rate Factor) values
- Keyframe settings
- Encoding parameters for different codecs (H.264, H.265, VP9)

View File

@@ -5,9 +5,13 @@
- [Downloading media](#downloading-media)
- [Adding captions/subtitles](#adding-captionssubtitles)
- [Search media](#search-media)
- [Using Timestamps for sharing](#using-timestamps-for-sharing)
- [Mentionning users in comments](#Mentionning-users-in-comments)
- [Show comments in the Timebar](#Show-comments-in-the-Timebar)
- [Share media](#share-media)
- [Embed media](#embed-media)
- [Customize my profile options](#customize-my-profile-options)
- [Trim videos](#trim-videos)
## Uploading media
@@ -195,6 +199,54 @@ You can now watch the captions/subtitles play back in the video player - and tog
<img src="./images/CC-display.png"/>
</p>
## Using Timestamps for sharing
### Using Timestamp in the URL
An additional GET parameter 't' can be added in video URL's to start the video at the given time. The starting time has to be given in seconds.
<p align="left">
<img src="./images/Demo1.png"/>
</p>
Additionally the share button has an option to generate the URL with the timestamp at current second the video is.
<p align="left">
<img src="./images/Demo2.png"/>
</p>
### Using Timestamp in the comments
Comments can also include timestamps. They are automatically detected upon posting the comment, and will be in the form of an hyperlink link in the comment. The timestamps in the comments have to follow the format HH:MM:SS or MM:SS
<p align="left">
<img src="./images/Demo3.png"/>
</p>
## Mentionning users in comments
Comments can also mention other users by tagging with '@'. This will open suggestion box showing usernames, and the selection will refine as the user continues typing.
Comments send with mentions will contain a link to the user page, and can be setup to send a mail to the mentionned user.
<p align="left">
<img src="./images/Mention1.png"/>
<img src="./images/Mention2.png"/>
<img src="./images/Mention3.png"/>
<img src="./images/Mention4.png"/>
</p>
## Show comments in the Timebar
When enabled, comments including a timestamp will also be displayed in the current video Timebar as a little colorful dot. The comment can be previewed by hovering the dot (left image) and it will be displayed on top of the video when reaching the correct time (right image).
Only comments with correct timestamps formats (HH:MM:SS or MM:SS) will be picked up and appear in the Timebar.
<p align="left">
<img src="./images/TimebarComments_Hover.png" height="180" alt="Comment preview on hover"/>
<img src="./images/TimebarComments_Hit.png" height="180" alt="Comment shown when the timestamp is reached "/>
</p>
## Search media
How search can be used
@@ -206,3 +258,7 @@ How to use the embed media option
## Customize my profile options
Customize profile and channel
## Trim videos
Once a video is uploaded, you can trim it to create a new video or to replace the original one. You can also create segments of the video, which will be available as separate videos. Edit the video and click on the "Trime Video" option. If the original video has finished processing (encodings are created for all resolutions), then this is an action that runs instantly. If the original video hasn't processed, which is the case when you upload a video and edit it right away, then the trim action will trigger processing of the video and will take some time to finish. In all cases, you get to see the original video (or the trimmed versions) immediately, so you are sure of what you have uploaded or trimmed, with a message that the video is being processed.

View File

@@ -1,4 +1,11 @@
from django import forms
from django.conf import settings
from django.contrib import admin
from django.core.exceptions import ValidationError
from django.db import transaction
from tinymce.widgets import TinyMCE
from rbac.models import RBACGroup
from .models import (
Category,
@@ -7,8 +14,12 @@ from .models import (
Encoding,
Language,
Media,
Page,
Subtitle,
Tag,
TinyMCEMedia,
TranscriptionRequest,
VideoTrimRequest,
)
@@ -40,15 +51,144 @@ 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"
class CategoryAdminForm(forms.ModelForm):
rbac_groups = forms.ModelMultipleChoiceField(queryset=RBACGroup.objects.all(), required=False, widget=admin.widgets.FilteredSelectMultiple('Groups', False))
class Meta:
model = Category
# LTI fields will be shown as read-only when USE_LTI is enabled
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
is_rbac_category = cleaned_data.get('is_rbac_category')
identity_provider = cleaned_data.get('identity_provider')
# Check if this category has any RBAC groups
if self.instance.pk:
has_rbac_groups = cleaned_data.get('rbac_groups')
else:
has_rbac_groups = False
if not is_rbac_category:
if has_rbac_groups:
cleaned_data['is_rbac_category'] = True
# self.add_error('is_rbac_category', ValidationError('This category has RBAC groups assigned. "Is RBAC Category" must be enabled.'))
for rbac_group in cleaned_data.get('rbac_groups'):
if rbac_group.identity_provider != identity_provider:
self.add_error('rbac_groups', ValidationError('Chosen Groups are associated with a different Identity Provider than the one selected here.'))
return cleaned_data
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
self.fields['rbac_groups'].initial = self.instance.rbac_groups.all()
def save(self, commit=True):
category = super().save(commit=True)
if commit:
self.save_m2m()
if self.instance.rbac_groups.exists() or self.cleaned_data.get('rbac_groups'):
if not self.cleaned_data['is_rbac_category']:
category.is_rbac_category = True
category.save(update_fields=['is_rbac_category'])
return category
@transaction.atomic
def save_m2m(self):
if self.instance.pk:
rbac_groups = self.cleaned_data['rbac_groups']
self._update_rbac_groups(rbac_groups)
def _update_rbac_groups(self, rbac_groups):
new_rbac_group_ids = RBACGroup.objects.filter(pk__in=rbac_groups).values_list('pk', flat=True)
existing_rbac_groups = RBACGroup.objects.filter(categories=self.instance)
existing_rbac_groups_ids = existing_rbac_groups.values_list('pk', flat=True)
rbac_groups_to_add = RBACGroup.objects.filter(pk__in=new_rbac_group_ids).exclude(pk__in=existing_rbac_groups_ids)
rbac_groups_to_remove = existing_rbac_groups.exclude(pk__in=new_rbac_group_ids)
for rbac_group in rbac_groups_to_add:
rbac_group.categories.add(self.instance)
for rbac_group in rbac_groups_to_remove:
rbac_group.categories.remove(self.instance)
class CategoryAdmin(admin.ModelAdmin):
search_fields = ["title"]
list_display = ["title", "user", "add_date", "is_global", "media_count"]
list_filter = ["is_global"]
form = CategoryAdminForm
search_fields = ["title", "uid"]
list_display = ["title", "user", "add_date", "media_count"]
list_filter = []
ordering = ("-add_date",)
readonly_fields = ("user", "media_count")
readonly_fields = ("user", "media_count", "lti_platform", "lti_context_id")
change_form_template = 'admin/files/category/change_form.html'
def get_list_filter(self, request):
list_filter = list(self.list_filter)
if getattr(settings, 'USE_RBAC', False):
list_filter.insert(0, "is_rbac_category")
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
list_filter.insert(-1, "identity_provider")
return list_filter
def get_list_display(self, request):
list_display = list(self.list_display)
if getattr(settings, 'USE_RBAC', False):
list_display.insert(-1, "is_rbac_category")
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
list_display.insert(-1, "identity_provider")
return list_display
def get_fieldsets(self, request, obj=None):
basic_fieldset = [
(
'Category Information',
{
'fields': ['uid', 'title', 'description', 'user', 'media_count', 'thumbnail', 'listings_thumbnail'],
},
),
]
additional_fieldsets = []
if getattr(settings, 'USE_LTI', False):
lti_fieldset = [
('LTI Integration', {'fields': ['lti_platform', 'lti_context_id'], 'classes': ['tab'], 'description': 'LTI/LMS integration settings (automatically managed by LTI provisioning)'}),
]
additional_fieldsets.extend(lti_fieldset)
if getattr(settings, 'USE_RBAC', False):
rbac_fieldset = [
('RBAC Settings', {'fields': ['is_rbac_category'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
]
if getattr(settings, 'USE_IDENTITY_PROVIDERS', False):
rbac_fieldset = [
('RBAC Settings', {'fields': ['is_rbac_category', 'identity_provider'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
]
additional_fieldsets.extend(rbac_fieldset)
return basic_fieldset + additional_fieldsets
class TagAdmin(admin.ModelAdmin):
@@ -70,11 +210,68 @@ class LanguageAdmin(admin.ModelAdmin):
class SubtitleAdmin(admin.ModelAdmin):
pass
list_display = ["id", "language", "media"]
list_filter = ["language"]
search_fields = ["media__title"]
readonly_fields = ("media", "user")
class VideoTrimRequestAdmin(admin.ModelAdmin):
list_display = ["media", "status", "add_date", "video_action", "media_trim_style", "timestamps"]
list_filter = ["status", "video_action", "media_trim_style", "add_date"]
search_fields = ["media__title"]
readonly_fields = ("add_date",)
ordering = ("-add_date",)
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"
class TranscriptionRequestAdmin(admin.ModelAdmin):
list_display = ["media", "add_date", "status", "translate_to_english"]
list_filter = ["status", "translate_to_english", "add_date"]
search_fields = ["media__title"]
readonly_fields = ("add_date", "logs")
ordering = ("-add_date",)
class PageAdminForm(forms.ModelForm):
description = forms.CharField(widget=TinyMCE())
def clean_description(self):
content = self.cleaned_data['description']
# Add sandbox attribute to all iframes
content = content.replace('<iframe ', '<iframe sandbox="allow-scripts allow-same-origin allow-presentation" ')
return content
class Meta:
model = Page
fields = "__all__"
class PageAdmin(admin.ModelAdmin):
form = PageAdminForm
@admin.register(TinyMCEMedia)
class TinyMCEMediaAdmin(admin.ModelAdmin):
list_display = ['original_filename', 'file_type', 'uploaded_at', 'user']
list_filter = ['file_type', 'uploaded_at']
search_fields = ['original_filename']
readonly_fields = ['uploaded_at']
date_hierarchy = 'uploaded_at'
admin.site.register(EncodeProfile, EncodeProfileAdmin)
@@ -82,6 +279,11 @@ admin.site.register(Comment, CommentAdmin)
admin.site.register(Media, MediaAdmin)
admin.site.register(Encoding, EncodingAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(Page, PageAdmin)
admin.site.register(Tag, TagAdmin)
admin.site.register(Subtitle, SubtitleAdmin)
admin.site.register(Language, LanguageAdmin)
admin.site.register(VideoTrimRequest, VideoTrimRequestAdmin)
admin.site.register(TranscriptionRequest, TranscriptionRequestAdmin)
Media._meta.app_config.verbose_name = "Media"

View File

@@ -15,7 +15,7 @@ class VideoEncodingError(Exception):
RE_TIMECODE = re.compile(r"time=(\d+:\d+:\d+.\d+)")
console_encoding = locale.getdefaultlocale()[1] or "UTF-8"
console_encoding = locale.getlocale()[1] or "UTF-8"
class FFmpegBackend(object):

Some files were not shown because too many files have changed in this diff Show More