mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
Compare commits
6 Commits
f65338562e
...
02eb712a50
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02eb712a50 | ||
|
|
8c73633429 | ||
|
|
496285e9e1 | ||
|
|
55c5b0be12 | ||
|
|
3c74badaec | ||
|
|
030e3cbe68 |
@ -1 +1 @@
|
||||
VERSION = "7.1.0"
|
||||
VERSION = "7.3.0"
|
||||
|
||||
@ -357,6 +357,10 @@ class Media(models.Model):
|
||||
a_tags,
|
||||
b_tags,
|
||||
]
|
||||
|
||||
for subtitle in self.subtitles.all():
|
||||
items.append(subtitle.subtitle_text)
|
||||
|
||||
items = [item for item in items if item]
|
||||
text = " ".join(items)
|
||||
text = " ".join([token for token in text.lower().split(" ") if token not in STOP_WORDS])
|
||||
@ -406,11 +410,11 @@ class Media(models.Model):
|
||||
self.media_type = "image"
|
||||
elif kind == "pdf":
|
||||
self.media_type = "pdf"
|
||||
|
||||
if self.media_type in ["audio", "image", "pdf"]:
|
||||
if self.media_type in ["image", "pdf"]:
|
||||
self.encoding_status = "success"
|
||||
else:
|
||||
ret = helpers.media_file_info(self.media_file.path)
|
||||
|
||||
if ret.get("fail"):
|
||||
self.media_type = ""
|
||||
self.encoding_status = "fail"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import pysubs2
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
@ -73,6 +74,17 @@ class Subtitle(models.Model):
|
||||
raise Exception("Could not convert to srt")
|
||||
return True
|
||||
|
||||
@property
|
||||
def subtitle_text(self):
|
||||
sub = pysubs2.load(self.subtitle_file.path, encoding="utf-8")
|
||||
text = ' '.join([line.text for line in sub])
|
||||
text = text.replace("\\N", " ")
|
||||
text = text.replace("-", " ")
|
||||
text = text.replace(".", " ")
|
||||
text = text.replace(" ", " ")
|
||||
|
||||
return text
|
||||
|
||||
|
||||
class TranscriptionRequest(models.Model):
|
||||
# Whisper transcription request
|
||||
|
||||
@ -980,10 +980,10 @@ def post_trim_action(friendly_token):
|
||||
produce_sprite_from_video.delay(friendly_token)
|
||||
create_hls.delay(friendly_token)
|
||||
|
||||
vt_request = VideoTrimRequest.objects.filter(media=media, status="running").first()
|
||||
if vt_request:
|
||||
vt_request.status = "success"
|
||||
vt_request.save(update_fields=["status"])
|
||||
vt_request = VideoTrimRequest.objects.filter(media=media, status="running").first()
|
||||
if vt_request:
|
||||
vt_request.status = "success"
|
||||
vt_request.save(update_fields=["status"])
|
||||
|
||||
return True
|
||||
|
||||
@ -1003,7 +1003,6 @@ def video_trim_task(self, trim_request_id):
|
||||
|
||||
timestamps_encodings = get_trim_timestamps(trim_request.media.trim_video_path, trim_request.timestamps)
|
||||
timestamps_original = get_trim_timestamps(trim_request.media.media_file.path, trim_request.timestamps)
|
||||
|
||||
if not timestamps_encodings:
|
||||
trim_request.status = "fail"
|
||||
trim_request.save(update_fields=["status"])
|
||||
|
||||
@ -174,15 +174,15 @@ class MediaList(APIView):
|
||||
media = Media.objects.none()
|
||||
else:
|
||||
base_queryset = Media.objects.prefetch_related("user", "tags")
|
||||
user_media_filters = {'permissions__user': request.user}
|
||||
media = base_queryset.filter(**user_media_filters)
|
||||
|
||||
# Build OR conditions similar to _get_media_queryset
|
||||
conditions = Q(permissions__user=request.user)
|
||||
|
||||
if getattr(settings, 'USE_RBAC', False):
|
||||
rbac_categories = request.user.get_rbac_categories_as_member()
|
||||
rbac_filters = {'category__in': rbac_categories}
|
||||
conditions |= Q(category__in=rbac_categories)
|
||||
|
||||
rbac_media = base_queryset.filter(**rbac_filters)
|
||||
media = media.union(rbac_media)
|
||||
media = base_queryset.filter(conditions).distinct()
|
||||
elif author_param:
|
||||
user_queryset = User.objects.all()
|
||||
user = get_object_or_404(user_queryset, username=author_param)
|
||||
@ -221,13 +221,12 @@ class MediaList(APIView):
|
||||
if publish_state and publish_state in ['private', 'public', 'unlisted']:
|
||||
media = media.filter(state=publish_state)
|
||||
|
||||
if show_param == "shared_with_me":
|
||||
media = media[:1000] # limit to 1000 results
|
||||
already_sorted = True
|
||||
|
||||
if not already_sorted:
|
||||
media = media.order_by(f"{ordering}{sort_by}")
|
||||
|
||||
if show_param == "shared_with_me":
|
||||
media = media[:1000] # limit to 1000 results
|
||||
|
||||
paginator = pagination_class()
|
||||
|
||||
page = paginator.paginate_queryset(media, request)
|
||||
|
||||
@ -430,7 +430,7 @@ def edit_video(request):
|
||||
return HttpResponseRedirect("/")
|
||||
|
||||
if media.media_type not in ["video", "audio"]:
|
||||
messages.add_message(request, messages.INFO, "Media is not video")
|
||||
messages.add_message(request, messages.INFO, "Media is not video or audio")
|
||||
return HttpResponseRedirect(media.get_absolute_url())
|
||||
|
||||
if not settings.ALLOW_VIDEO_TRIMMER:
|
||||
|
||||
@ -87,7 +87,7 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}`);
|
||||
const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}&exclude_self=True`);
|
||||
if (!response.ok) {
|
||||
throw new Error(translateString('Failed to search users'));
|
||||
}
|
||||
@ -110,6 +110,12 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
||||
clearTimeout(searchTimeout);
|
||||
}
|
||||
|
||||
// Only search if 3 or more characters
|
||||
if (value.trim().length < 3) {
|
||||
setSearchResults([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new timeout for debounced search
|
||||
const timeout = setTimeout(() => {
|
||||
searchUsers(value);
|
||||
@ -231,7 +237,7 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
||||
<div className="search-box">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={translateString('Search users to add...')}
|
||||
placeholder={translateString('Search users to add (min 3 characters)...')}
|
||||
value={addSearchTerm}
|
||||
onChange={(e) => handleAddSearchChange(e.target.value)}
|
||||
/>
|
||||
|
||||
@ -696,6 +696,11 @@ a.item-edit-link {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Show all checkboxes when any item has a selection
|
||||
&.has-any-selection .item-selection-checkbox {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Add hover shadow when any selection is active
|
||||
&.has-any-selection:not(.selected):hover {
|
||||
.item-content {
|
||||
|
||||
@ -27,15 +27,33 @@ export function MediaItem(props) {
|
||||
(props.isSelected ? ' selected' : '') +
|
||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||
|
||||
const handleItemClick = (e) => {
|
||||
// If there's any selection active, clicking the item should toggle selection
|
||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||
if (e.target.closest('.item-selection-checkbox') ||
|
||||
e.target.closest('.item-edit-icon') ||
|
||||
e.target.closest('.item-view-icon')) {
|
||||
return; // Let these elements handle their own clicks
|
||||
}
|
||||
|
||||
// Prevent all other clicks and toggle selection
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.onCheckboxChange({ target: { checked: !props.isSelected } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={finalClassname}>
|
||||
<div className={finalClassname} onClick={handleItemClick}>
|
||||
<div className="item-content">
|
||||
{props.showSelection && (
|
||||
<div className="item-selection-checkbox">
|
||||
<div className="item-selection-checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.isSelected || false}
|
||||
onChange={(e) => { props.onCheckboxChange && props.onCheckboxChange(e); }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label="Select media"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -70,8 +70,25 @@ export function MediaItemAudio(props) {
|
||||
(props.isSelected ? ' selected' : '') +
|
||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||
|
||||
const handleItemClick = (e) => {
|
||||
// If there's any selection active, clicking the item should toggle selection
|
||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||
if (e.target.closest('.item-selection-checkbox') ||
|
||||
e.target.closest('.item-edit-icon') ||
|
||||
e.target.closest('.item-view-icon')) {
|
||||
return; // Let these elements handle their own clicks
|
||||
}
|
||||
|
||||
// Prevent all other clicks and toggle selection
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.onCheckboxChange({ target: { checked: !props.isSelected } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={finalClassname}>
|
||||
<div className={finalClassname} onClick={handleItemClick}>
|
||||
{playlistOrderNumberComponent()}
|
||||
|
||||
<div className="item-content">
|
||||
|
||||
@ -77,8 +77,25 @@ export function MediaItemVideo(props) {
|
||||
(props.isSelected ? ' selected' : '') +
|
||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||
|
||||
const handleItemClick = (e) => {
|
||||
// If there's any selection active, clicking the item should toggle selection
|
||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||
if (e.target.closest('.item-selection-checkbox') ||
|
||||
e.target.closest('.item-edit-icon') ||
|
||||
e.target.closest('.item-view-icon')) {
|
||||
return; // Let these elements handle their own clicks
|
||||
}
|
||||
|
||||
// Prevent all other clicks and toggle selection
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.onCheckboxChange({ target: { checked: !props.isSelected } });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={finalClassname}>
|
||||
<div className={finalClassname} onClick={handleItemClick}>
|
||||
{playlistOrderNumberComponent()}
|
||||
|
||||
<div className="item-content">
|
||||
|
||||
@ -414,10 +414,22 @@ class NavMenuInlineTabs extends React.PureComponent {
|
||||
) : null}
|
||||
{this.props.onToggleSortingClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
|
||||
<li className="media-sorting-toggle">
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} onClick={this.props.onToggleSortingClick} title={translateString('Sort By')}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} onClick={this.props.onToggleSortingClick} title={translateString('Sort By')}>
|
||||
<CircleIconButton buttonShadow={false}>
|
||||
<i className="material-icons">swap_vert</i>
|
||||
</CircleIconButton>
|
||||
{this.props.hasActiveSort ? (
|
||||
<span style={{
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
right: '8px',
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'var(--default-theme-color)',
|
||||
border: '2px solid white',
|
||||
}}></span>
|
||||
) : null}
|
||||
</span>
|
||||
</li>
|
||||
) : null}
|
||||
@ -438,6 +450,7 @@ NavMenuInlineTabs.propTypes = {
|
||||
onToggleSortingClick: PropTypes.func,
|
||||
hasActiveFilters: PropTypes.bool,
|
||||
hasActiveTags: PropTypes.bool,
|
||||
hasActiveSort: PropTypes.bool,
|
||||
};
|
||||
|
||||
function AddBannerButton(props) {
|
||||
@ -663,6 +676,7 @@ export default function ProfilePagesHeader(props) {
|
||||
onToggleSortingClick={props.onToggleSortingClick}
|
||||
hasActiveFilters={props.hasActiveFilters}
|
||||
hasActiveTags={props.hasActiveTags}
|
||||
hasActiveSort={props.hasActiveSort}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -678,6 +692,7 @@ ProfilePagesHeader.propTypes = {
|
||||
onToggleSortingClick: PropTypes.func,
|
||||
hasActiveFilters: PropTypes.bool,
|
||||
hasActiveTags: PropTypes.bool,
|
||||
hasActiveSort: PropTypes.bool,
|
||||
};
|
||||
|
||||
ProfilePagesHeader.defaultProps = {
|
||||
|
||||
@ -67,6 +67,9 @@ export function ProfileMediaFilters(props) {
|
||||
}
|
||||
|
||||
function onFilterSelect(ev) {
|
||||
const filterType = ev.currentTarget.getAttribute('filter');
|
||||
const clickedValue = ev.currentTarget.getAttribute('value');
|
||||
|
||||
const args = {
|
||||
media_type: mediaTypeFilter,
|
||||
upload_date: uploadDateFilter,
|
||||
@ -76,34 +79,35 @@ export function ProfileMediaFilters(props) {
|
||||
tag: props.selectedTag || tagFilter,
|
||||
};
|
||||
|
||||
switch (ev.currentTarget.getAttribute('filter')) {
|
||||
switch (filterType) {
|
||||
case 'media_type':
|
||||
args.media_type = ev.currentTarget.getAttribute('value');
|
||||
// If clicking the currently selected filter, deselect it (set to 'all')
|
||||
args.media_type = clickedValue === mediaTypeFilter ? 'all' : clickedValue;
|
||||
props.onFiltersUpdate(args);
|
||||
setFilter_media_type(args.media_type);
|
||||
break;
|
||||
case 'upload_date':
|
||||
args.upload_date = ev.currentTarget.getAttribute('value');
|
||||
args.upload_date = clickedValue === uploadDateFilter ? 'all' : clickedValue;
|
||||
props.onFiltersUpdate(args);
|
||||
setFilter_upload_date(args.upload_date);
|
||||
break;
|
||||
case 'duration':
|
||||
args.duration = ev.currentTarget.getAttribute('value');
|
||||
args.duration = clickedValue === durationFilter ? 'all' : clickedValue;
|
||||
props.onFiltersUpdate(args);
|
||||
setFilter_duration(args.duration);
|
||||
break;
|
||||
case 'publish_state':
|
||||
args.publish_state = ev.currentTarget.getAttribute('value');
|
||||
args.publish_state = clickedValue === publishStateFilter ? 'all' : clickedValue;
|
||||
props.onFiltersUpdate(args);
|
||||
setFilter_publish_state(args.publish_state);
|
||||
break;
|
||||
case 'sort_by':
|
||||
args.sort_by = ev.currentTarget.getAttribute('value');
|
||||
args.sort_by = clickedValue === sortByFilter ? 'date_added_desc' : clickedValue;
|
||||
props.onFiltersUpdate(args);
|
||||
setFilter_sort_by(args.sort_by);
|
||||
break;
|
||||
case 'tag':
|
||||
args.tag = ev.currentTarget.getAttribute('value');
|
||||
args.tag = clickedValue === tagFilter ? 'all' : clickedValue;
|
||||
props.onFiltersUpdate(args);
|
||||
setFilter_tag(args.tag);
|
||||
break;
|
||||
|
||||
@ -26,8 +26,10 @@ export function ProfileMediaTags(props) {
|
||||
|
||||
function onFilterSelect(ev) {
|
||||
const tag = ev.currentTarget.getAttribute('value');
|
||||
setFilter_tag(tag);
|
||||
props.onTagSelect(tag);
|
||||
// If clicking the currently selected tag, deselect it (set to 'all')
|
||||
const newTag = tag === tagFilter ? 'all' : tag;
|
||||
setFilter_tag(newTag);
|
||||
props.onTagSelect(newTag);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -605,7 +605,7 @@ export class ProfileMediaPage extends Page {
|
||||
}
|
||||
|
||||
onTagSelect(tag) {
|
||||
this.setState({ selectedTag: tag, hiddenTags: true }, () => {
|
||||
this.setState({ selectedTag: tag }, () => {
|
||||
// Apply tag filter
|
||||
this.onFiltersUpdate({
|
||||
media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null,
|
||||
@ -617,7 +617,7 @@ export class ProfileMediaPage extends Page {
|
||||
}
|
||||
|
||||
onSortSelect(sortOption) {
|
||||
this.setState({ selectedSort: sortOption, hiddenSorting: true }, () => {
|
||||
this.setState({ selectedSort: sortOption }, () => {
|
||||
// Apply sort filter
|
||||
this.onFiltersUpdate({
|
||||
media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null,
|
||||
@ -884,12 +884,15 @@ export class ProfileMediaPage extends Page {
|
||||
);
|
||||
|
||||
const hasActiveTags = this.state.selectedTag && this.state.selectedTag !== 'all';
|
||||
const hasActiveSort = this.state.selectedSort && this.state.selectedSort !== 'date_added_desc';
|
||||
|
||||
console.log('Filter Debug:', {
|
||||
filterArgs: this.state.filterArgs,
|
||||
selectedTag: this.state.selectedTag,
|
||||
selectedSort: this.state.selectedSort,
|
||||
hasActiveFilters,
|
||||
hasActiveTags
|
||||
hasActiveTags,
|
||||
hasActiveSort
|
||||
});
|
||||
|
||||
return [
|
||||
@ -904,6 +907,7 @@ export class ProfileMediaPage extends Page {
|
||||
onToggleSortingClick={this.onToggleSortingClick}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
hasActiveTags={hasActiveTags}
|
||||
hasActiveSort={hasActiveSort}
|
||||
/>
|
||||
) : null,
|
||||
this.state.author ? (
|
||||
|
||||
@ -151,17 +151,23 @@ export class ProfileSharedByMePage extends Page {
|
||||
onToggleFiltersClick() {
|
||||
this.setState({
|
||||
hiddenFilters: !this.state.hiddenFilters,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: true,
|
||||
});
|
||||
}
|
||||
|
||||
onToggleTagsClick() {
|
||||
this.setState({
|
||||
hiddenFilters: true,
|
||||
hiddenTags: !this.state.hiddenTags,
|
||||
hiddenSorting: true,
|
||||
});
|
||||
}
|
||||
|
||||
onToggleSortingClick() {
|
||||
this.setState({
|
||||
hiddenFilters: true,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: !this.state.hiddenSorting,
|
||||
});
|
||||
}
|
||||
@ -290,6 +296,12 @@ export class ProfileSharedByMePage extends Page {
|
||||
|
||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||
|
||||
// Check if any filters are active
|
||||
const hasActiveFilters = this.state.filterArgs && (
|
||||
this.state.filterArgs.includes('media_type=') ||
|
||||
this.state.filterArgs.includes('upload_date=')
|
||||
);
|
||||
|
||||
return [
|
||||
this.state.author ? (
|
||||
<ProfilePagesHeader
|
||||
@ -300,6 +312,9 @@ export class ProfileSharedByMePage extends Page {
|
||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||
onToggleTagsClick={this.onToggleTagsClick}
|
||||
onToggleSortingClick={this.onToggleSortingClick}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||
/>
|
||||
) : null,
|
||||
this.state.author ? (
|
||||
|
||||
@ -151,17 +151,23 @@ export class ProfileSharedWithMePage extends Page {
|
||||
onToggleFiltersClick() {
|
||||
this.setState({
|
||||
hiddenFilters: !this.state.hiddenFilters,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: true,
|
||||
});
|
||||
}
|
||||
|
||||
onToggleTagsClick() {
|
||||
this.setState({
|
||||
hiddenFilters: true,
|
||||
hiddenTags: !this.state.hiddenTags,
|
||||
hiddenSorting: true,
|
||||
});
|
||||
}
|
||||
|
||||
onToggleSortingClick() {
|
||||
this.setState({
|
||||
hiddenFilters: true,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: !this.state.hiddenSorting,
|
||||
});
|
||||
}
|
||||
@ -290,6 +296,12 @@ export class ProfileSharedWithMePage extends Page {
|
||||
|
||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||
|
||||
// Check if any filters are active
|
||||
const hasActiveFilters = this.state.filterArgs && (
|
||||
this.state.filterArgs.includes('media_type=') ||
|
||||
this.state.filterArgs.includes('upload_date=')
|
||||
);
|
||||
|
||||
return [
|
||||
this.state.author ? (
|
||||
<ProfilePagesHeader
|
||||
@ -300,6 +312,9 @@ export class ProfileSharedWithMePage extends Page {
|
||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||
onToggleTagsClick={this.onToggleTagsClick}
|
||||
onToggleSortingClick={this.onToggleSortingClick}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||
/>
|
||||
) : null,
|
||||
this.state.author ? (
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -206,6 +206,7 @@ class UserList(APIView):
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
|
||||
openapi.Parameter(name='name', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Search by name or username'),
|
||||
openapi.Parameter(name='exclude_self', type=openapi.TYPE_BOOLEAN, in_=openapi.IN_QUERY, description='Exclude current user from results'),
|
||||
],
|
||||
tags=['Users'],
|
||||
operation_summary='List users',
|
||||
@ -226,6 +227,11 @@ class UserList(APIView):
|
||||
if name:
|
||||
users = users.filter(Q(name__icontains=name) | Q(username__icontains=name))
|
||||
|
||||
# Exclude current user if requested
|
||||
exclude_self = request.GET.get("exclude_self", "") == "True"
|
||||
if exclude_self and request.user.is_authenticated:
|
||||
users = users.exclude(id=request.user.id)
|
||||
|
||||
if settings.USERS_NEEDS_TO_BE_APPROVED:
|
||||
is_approved = request.GET.get("is_approved")
|
||||
if is_approved == "true":
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user