Skip to content

Commit e790795

Browse files
authored
feat: bulk actions API
1 parent de99d84 commit e790795

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+3879
-2689
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.PHONY: admin-shell build-frontend
22

33
admin-shell:
4-
@container_id=$$(docker-compose ps -q web); \
4+
@container_id=$$(docker compose ps -q web); \
55
if [ -z "$$container_id" ]; then \
66
echo "Web container not found"; \
77
exit 1; \

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
101101
* [Configuration](docs/admins_docs.md#5-configuration) page
102102
* [Transcoding](docs/transcoding.md) page
103103
* [Developer Experience](docs/dev_exp.md) page
104+
* [Media Permissions](docs/media_permissions.md) page
104105

105106

106107
## Technology

cms/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,9 @@
498498

499499
ALLOW_CUSTOM_MEDIA_URLS = False
500500

501+
# Whether to allow anonymous users to list all users
502+
ALLOW_ANONYMOUS_USER_LISTING = True
503+
501504
# ffmpeg options
502505
FFMPEG_DEFAULT_PRESET = "medium" # see https://trac.ffmpeg.org/wiki/Encode/H.264
503506

cms/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "6.3.0"
1+
VERSION = "6.4.0"

docs/admins_docs.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ By default, all these services are enabled, but in order to create a scaleable d
168168

169169
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.
170170

171-
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`
172-
173171
To run, update the configs above if necessary, build the image by running `docker compose build`, then run `docker compose run`
174172

175173
### Simple Deployment, accessed as http://localhost
@@ -502,6 +500,16 @@ By default `CAN_COMMENT = "all"` means that all registered users can add comment
502500

503501
- **advancedUser**, only users that are marked as advanced users can add comment. Admins or MediaCMS managers can make users advanced users by editing their profile and selecting advancedUser.
504502

503+
### 5.26 Control whether anonymous users can list all users
504+
505+
By default, anonymous users can view the list of all users on the platform. To restrict this to authenticated users only, set:
506+
507+
```
508+
ALLOW_ANONYMOUS_USER_LISTING = False
509+
```
510+
511+
When set to False, only logged-in users will be able to access the user listing API endpoint.
512+
505513

506514
## 6. Manage pages
507515
to be written
@@ -967,4 +975,4 @@ Visiting the admin, you will see the Identity Providers tab and you can add one.
967975

968976
## 25. Custom urls
969977
To enable custom urls, set `ALLOW_CUSTOM_MEDIA_URLS = True` on settings.py or local_settings.py
970-
This will enable editing the URL of the media, while editing a media. If the URL is already taken you get a message you cannot update this.
978+
This will enable editing the URL of the media, while editing a media. If the URL is already taken you get a message you cannot update this.

docs/dev_exp.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ There is ongoing effort to provide a better developer experience and document it
44
## How to develop locally with Docker
55
First install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
66

7-
Then run `docker-compose -f docker-compose-dev.yaml up`
7+
Then run `docker compose -f docker-compose-dev.yaml up`
88

99
```
10-
user@user:~/mediacms$ docker-compose -f docker-compose-dev.yaml up
10+
user@user:~/mediacms$ docker compose -f docker-compose-dev.yaml up
1111
```
1212

1313
In a few minutes the app will be available at http://localhost . Login via admin/admin
@@ -37,7 +37,7 @@ Django starts at http://localhost and is reloading automatically. Making any cha
3737
If Django breaks due to an error (eg SyntaxError, while editing the code), you might have to restart it
3838

3939
```
40-
docker-compose -f docker-compose-dev.yaml restart web
40+
docker compose -f docker-compose-dev.yaml restart web
4141
```
4242

4343

@@ -62,9 +62,9 @@ In order to make changes to React code, edit code on frontend/src and check it's
6262
### Development workflow with the frontend
6363
1. Edit frontend/src/ files
6464
2. Check changes on http://localhost:8088/
65-
3. Build frontend with `docker-compose -f docker-compose-dev.yaml exec frontend npm run dist`
65+
3. Build frontend with `docker compose -f docker-compose-dev.yaml exec frontend npm run dist`
6666
4. Copy static files to Django static folder with`cp -r frontend/dist/static/* static/`
67-
5. Restart Django - `docker-compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
67+
5. Restart Django - `docker compose -f docker-compose-dev.yaml restart web` so that it uses the new static files
6868
6. Commit the changes
6969

7070
### Helper commands
@@ -81,7 +81,7 @@ Build the frontend:
8181

8282
```
8383
user@user:~/mediacms$ make build-frontend
84-
docker-compose -f docker-compose-dev.yaml exec frontend npm run dist
84+
docker compose -f docker-compose-dev.yaml exec frontend npm run dist
8585
8686
> [email protected] dist /home/mediacms.io/mediacms/frontend
8787
> mediacms-scripts rimraf ./dist && mediacms-scripts build --config=./config/mediacms.config.js --env=dist

docs/developers_docs.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ to be written
1717

1818
## 3. API documentation
1919
API is documented using Swagger - checkout ot http://your_installation/swagger - example https://demo.mediacms.io/swagger/
20-
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
20+
This page allows you to login to perform authenticated actions - it will also use your session if logged in.
2121

2222

2323
An example of working with Python requests library:
@@ -50,8 +50,8 @@ Checkout the [Code of conduct page](../CODE_OF_CONDUCT.md) if you want to contri
5050
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)
5151

5252
```
53-
docker-compose -f docker-compose-dev.yaml build
54-
docker-compose -f docker-compose-dev.yaml up
53+
docker compose -f docker-compose-dev.yaml build
54+
docker compose -f docker-compose-dev.yaml up
5555
```
5656

5757
An `admin` user is created during the installation process. Its attributes are defined in `docker-compose-dev.yaml`:
@@ -65,16 +65,16 @@ ADMIN_EMAIL: 'admin@localhost'
6565
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
6666

6767
```
68-
docker-compose -f docker-compose-dev.yaml exec -T frontend npm run dist
68+
docker compose -f docker-compose-dev.yaml exec -T frontend npm run dist
6969
```
7070

71-
And then in order for the changes to be visible on the application while served through nginx,
71+
And then in order for the changes to be visible on the application while served through nginx,
7272

7373
```
7474
cp -r frontend/dist/static/* static/
7575
```
7676

77-
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.
77+
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.
7878
Make sure the urls are set on `frontend/.env` if different than localhost
7979

8080

@@ -90,7 +90,7 @@ http://localhost:8088/manage-media.html manage_media
9090
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
9191

9292
```
93-
docker-compose -f docker-compose-dev.yaml restart web
93+
docker compose -f docker-compose-dev.yaml restart web
9494
```
9595

9696
## How video is transcoded
@@ -113,7 +113,7 @@ there is also an experimental small service (not commited to the repo currently)
113113

114114
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
115115

116-
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.
116+
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.
117117

118118
## 6. Working with the automated tests
119119

@@ -122,19 +122,19 @@ This instructions assume that you're using the docker installation
122122
1. start docker-compose
123123

124124
```
125-
docker-compose up
125+
docker compose up
126126
```
127127

128128
2. Install the requirements on `requirements-dev.txt ` on web container (we'll use the web container for this)
129129

130130
```
131-
docker-compose exec -T web pip install -r requirements-dev.txt
131+
docker compose exec -T web pip install -r requirements-dev.txt
132132
```
133133

134134
3. Now you can run the existing tests
135135

136136
```
137-
docker-compose exec --env TESTING=True -T web pytest
137+
docker compose exec --env TESTING=True -T web pytest
138138
```
139139

140140
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)
@@ -143,13 +143,13 @@ The `TESTING=True` is passed for Django to be aware this is a testing environmen
143143
4. You may try a single test, by specifying the path, for example
144144

145145
```
146-
docker-compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
146+
docker compose exec --env TESTING=True -T web pytest tests/test_fixtures.py
147147
```
148148

149149
5. You can also see the coverage
150150

151151
```
152-
docker-compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
152+
docker compose exec --env TESTING=True -T web pytest --cov=. --cov-report=html
153153
```
154154

155155
and of course...you are very welcome to help us increase it ;)

docs/media_permissions.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Media Permissions in MediaCMS
2+
3+
This document explains the permission system in MediaCMS, which controls who can view, edit, and manage media files.
4+
5+
## Overview
6+
7+
MediaCMS provides a flexible permission system that allows fine-grained control over media access. The system supports:
8+
9+
1. **Basic permissions** - Public, private, and unlisted media
10+
2. **User-specific permissions** - Direct permissions granted to specific users
11+
3. **Role-Based Access Control (RBAC)** - Category-based permissions through group membership
12+
13+
## Media States
14+
15+
Every media file has a state that determines its basic visibility:
16+
17+
- **Public** - Visible to everyone
18+
- **Private** - Only visible to the owner and users with explicit permissions
19+
- **Unlisted** - Not listed in public listings but accessible via direct link
20+
21+
22+
## User Roles
23+
24+
MediaCMS has several user roles that affect permissions:
25+
26+
- **Regular User** - Can upload and manage their own media
27+
- **Advanced User** - Additional capabilities (configurable)
28+
- **MediaCMS Editor** - Can edit and review content across the platform
29+
- **MediaCMS Manager** - Full management capabilities
30+
- **Admin** - Complete system access
31+
32+
## Direct Media Permissions
33+
34+
The `MediaPermission` model allows granting specific permissions to individual users:
35+
36+
### Permission Levels
37+
38+
- **Viewer** - Can view the media even if it's private
39+
- **Editor** - Can view and edit the media's metadata
40+
- **Owner** - Full control, including deletion
41+
42+
## Role-Based Access Control (RBAC)
43+
44+
When RBAC is enabled (`USE_RBAC` setting), permissions can be managed through categories and groups:
45+
46+
1. Categories can be marked as RBAC-controlled
47+
2. Users are assigned to RBAC groups with specific roles
48+
3. RBAC groups are associated with categories
49+
4. Users inherit permissions to media in those categories based on their role
50+
51+
### RBAC Roles
52+
53+
- **Member** - Can view media in the category
54+
- **Contributor** - Can view and edit media in the category
55+
- **Manager** - Full control over media in the category
56+
57+
## Permission Checking Methods
58+
59+
The User model provides several methods to check permissions:
60+
61+
```python
62+
# From users/models.py
63+
def has_member_access_to_media(self, media):
64+
# Check if user can view the media
65+
# ...
66+
67+
def has_contributor_access_to_media(self, media):
68+
# Check if user can edit the media
69+
# ...
70+
71+
def has_owner_access_to_media(self, media):
72+
# Check if user has full control over the media
73+
# ...
74+
```
75+
76+
## How Permissions Are Applied
77+
78+
When a user attempts to access media, the system checks permissions in this order:
79+
80+
1. Is the media public? If yes, allow access.
81+
2. Is the user the owner of the media? If yes, allow full access.
82+
3. Does the user have direct permissions through MediaPermission? If yes, grant the corresponding access level.
83+
4. If RBAC is enabled, does the user have access through category membership? If yes, grant the corresponding access level.
84+
5. If none of the above, deny access.
85+
86+
## Media Sharing
87+
88+
Users can share media with others by:
89+
90+
1. Making it public or unlisted
91+
2. Granting direct permissions to specific users
92+
3. Adding it to a category that's accessible to an RBAC group
93+
94+
## Implementation Details
95+
96+
### Media Listing
97+
98+
When listing media, the system filters based on permissions:
99+
100+
```python
101+
# Simplified example from files/views/media.py
102+
def _get_media_queryset(self, request, user=None):
103+
# 1. Public media
104+
listable_media = Media.objects.filter(listable=True)
105+
106+
if not request.user.is_authenticated:
107+
return listable_media
108+
109+
# 2. User permissions for authenticated users
110+
user_media = Media.objects.filter(permissions__user=request.user)
111+
112+
# 3. RBAC for authenticated users
113+
if getattr(settings, 'USE_RBAC', False):
114+
rbac_categories = request.user.get_rbac_categories_as_member()
115+
rbac_media = Media.objects.filter(category__in=rbac_categories)
116+
117+
# Combine all accessible media
118+
return listable_media.union(user_media, rbac_media)
119+
```
120+
121+
### Permission Checking
122+
123+
The system uses helper methods to check permissions:
124+
125+
```python
126+
# From users/models.py
127+
def has_member_access_to_media(self, media):
128+
# First check if user is the owner
129+
if media.user == self:
130+
return True
131+
132+
# Then check RBAC permissions
133+
if getattr(settings, 'USE_RBAC', False):
134+
rbac_groups = RBACGroup.objects.filter(
135+
memberships__user=self,
136+
memberships__role__in=["member", "contributor", "manager"],
137+
categories__in=media.category.all()
138+
).distinct()
139+
if rbac_groups.exists():
140+
return True
141+
142+
# Then check MediaShare permissions for any access
143+
media_permission_exists = MediaPermission.objects.filter(
144+
user=self,
145+
media=media,
146+
).exists()
147+
148+
return media_permission_exists
149+
```
150+
151+
## Best Practices
152+
153+
1. **Default to Private** - Consider setting new uploads to private by default
154+
2. **Use Categories** - Organize media into categories for easier permission management
155+
3. **RBAC for Teams** - Use RBAC for team collaboration scenarios
156+
4. **Direct Permissions for Exceptions** - Use direct permissions for one-off sharing
157+
158+
## Configuration
159+
160+
The permission system can be configured through several settings:
161+
162+
- `USE_RBAC` - Enable/disable Role-Based Access Control
163+
164+
## Conclusion
165+
166+
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.

files/forms.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,18 @@ def __init__(self, user, *args, **kwargs):
6868
self.helper.form_method = 'post'
6969
self.helper.form_enctype = "multipart/form-data"
7070
self.helper.form_show_errors = False
71-
self.helper.layout = Layout(
71+
72+
layout_fields = [
7273
CustomField('title'),
7374
CustomField('new_tags'),
7475
CustomField('add_date'),
7576
CustomField('description'),
76-
CustomField('uploaded_poster'),
7777
CustomField('enable_comments'),
78-
)
78+
]
79+
if self.instance.media_type != "image":
80+
layout_fields.append(CustomField('uploaded_poster'))
81+
82+
self.helper.layout = Layout(*layout_fields)
7983

8084
if self.instance.media_type == "video":
8185
self.helper.layout.append(CustomField('thumbnail_time'))

0 commit comments

Comments
 (0)