kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
See #212: user detail profile
rodzic
8e6b6f454a
commit
8636b456a8
|
@ -70,8 +70,16 @@ class ManageTrackFileActionSerializer(common_serializers.ActionSerializer):
|
|||
return objects.delete()
|
||||
|
||||
|
||||
class PermissionsSerializer(serializers.Serializer):
|
||||
def to_representation(self, o):
|
||||
return o.get_permissions(defaults=self.context.get("default_permissions"))
|
||||
|
||||
def to_internal_value(self, o):
|
||||
return {"permissions": o}
|
||||
|
||||
|
||||
class ManageUserSerializer(serializers.ModelSerializer):
|
||||
permissions = serializers.SerializerMethodField()
|
||||
permissions = PermissionsSerializer(source="*")
|
||||
|
||||
class Meta:
|
||||
model = users_models.User
|
||||
|
@ -97,5 +105,13 @@ class ManageUserSerializer(serializers.ModelSerializer):
|
|||
"last_activity",
|
||||
]
|
||||
|
||||
def get_permissions(self, o):
|
||||
return o.get_permissions(defaults=self.context.get("default_permissions"))
|
||||
def update(self, instance, validated_data):
|
||||
instance = super().update(instance, validated_data)
|
||||
permissions = validated_data.pop("permissions", {})
|
||||
if permissions:
|
||||
for p, value in permissions.items():
|
||||
setattr(instance, "permission_{}".format(p), value)
|
||||
instance.save(
|
||||
update_fields=["permission_{}".format(p) for p in permissions.keys()]
|
||||
)
|
||||
return instance
|
||||
|
|
|
@ -94,6 +94,10 @@ class User(AbstractUser):
|
|||
perms[p] = v
|
||||
return perms
|
||||
|
||||
@property
|
||||
def all_permissions(self):
|
||||
return self.get_permissions()
|
||||
|
||||
def has_permissions(self, *perms, **kwargs):
|
||||
operator = kwargs.pop("operator", "and")
|
||||
if operator not in ["and", "or"]:
|
||||
|
|
|
@ -8,3 +8,26 @@ def test_manage_track_file_action_delete(factories):
|
|||
s.handle_delete(tfs.__class__.objects.all())
|
||||
|
||||
assert tfs.__class__.objects.count() == 0
|
||||
|
||||
|
||||
def test_user_update_permission(factories):
|
||||
user = factories["users.User"](
|
||||
permission_library=False,
|
||||
permission_upload=False,
|
||||
permission_federation=True,
|
||||
permission_settings=True,
|
||||
is_active=True,
|
||||
)
|
||||
s = serializers.ManageUserSerializer(
|
||||
user,
|
||||
data={"is_active": False, "permissions": {"federation": False, "upload": True}},
|
||||
)
|
||||
s.is_valid(raise_exception=True)
|
||||
s.save()
|
||||
user.refresh_from_db()
|
||||
|
||||
assert user.is_active is False
|
||||
assert user.permission_federation is False
|
||||
assert user.permission_upload is True
|
||||
assert user.permission_library is False
|
||||
assert user.permission_settings is True
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</template>
|
||||
<template slot="row-cells" slot-scope="scope">
|
||||
<td>
|
||||
<span>{{ scope.obj.username }}</span>
|
||||
<router-link :to="{name: 'manage.users.detail', params: {id: scope.obj.id }}">{{ scope.obj.username }}</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<span>{{ scope.obj.email }}</span>
|
||||
|
|
|
@ -32,6 +32,7 @@ import AdminSettings from '@/views/admin/Settings'
|
|||
import AdminLibraryBase from '@/views/admin/library/Base'
|
||||
import AdminLibraryFilesList from '@/views/admin/library/FilesList'
|
||||
import AdminUsersBase from '@/views/admin/users/Base'
|
||||
import AdminUsersDetail from '@/views/admin/users/UsersDetail'
|
||||
import AdminUsersList from '@/views/admin/users/UsersList'
|
||||
import FederationBase from '@/views/federation/Base'
|
||||
import FederationScan from '@/views/federation/Scan'
|
||||
|
@ -190,6 +191,12 @@ export default new Router({
|
|||
path: '',
|
||||
name: 'manage.users.list',
|
||||
component: AdminUsersList
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
name: 'manage.users.detail',
|
||||
component: AdminUsersDetail,
|
||||
props: true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="isLoading" class="ui vertical segment">
|
||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||
</div>
|
||||
<template v-if="object">
|
||||
<div :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']" v-title="object.username">
|
||||
<div class="segment-content">
|
||||
<h2 class="ui center aligned icon header">
|
||||
<i class="circular inverted user red icon"></i>
|
||||
<div class="content">
|
||||
@{{ object.username }}
|
||||
</div>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="ui hidden divider"></div>
|
||||
<div class="ui one column centered grid">
|
||||
<table class="ui collapsing very basic table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $t('Name') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ object.name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $t('Email address') }}
|
||||
</td>
|
||||
<td>
|
||||
{{ object.email }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $t('Sign-up') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date :date="object.date_joined"></human-date>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $t('Last activity') }}
|
||||
</td>
|
||||
<td>
|
||||
<human-date v-if="object.last_activity" :date="object.last_activity"></human-date>
|
||||
<template v-else>{{ $t('N/A') }}</template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $t('Account active') }}
|
||||
<span :data-tooltip="$t('Determine if the user account is active or not. Inactive users cannot login or user the service.')"><i class="question circle icon"></i></span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui toggle checkbox">
|
||||
<input
|
||||
@change="update('is_active')"
|
||||
v-model="object.is_active" type="checkbox">
|
||||
<label></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ $t('Permissions') }}
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
@change="update('permissions')"
|
||||
v-model="permissions"
|
||||
multiple
|
||||
class="ui search selection dropdown">
|
||||
<option v-for="p in allPermissions" :value="p.code">{{ p.label }}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui hidden divider"></div>
|
||||
<button @click="fetchData" class="ui basic button">{{ $t('Refresh') }}</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import $ from 'jquery'
|
||||
import axios from 'axios'
|
||||
import logger from '@/logging'
|
||||
|
||||
export default {
|
||||
props: ['id'],
|
||||
data () {
|
||||
return {
|
||||
isLoading: true,
|
||||
object: null,
|
||||
permissions: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
fetchData () {
|
||||
var self = this
|
||||
this.isLoading = true
|
||||
let url = 'manage/users/users/' + this.id + '/'
|
||||
axios.get(url).then((response) => {
|
||||
self.object = response.data
|
||||
self.permissions = []
|
||||
self.allPermissions.forEach(p => {
|
||||
if (self.object.permissions[p.code]) {
|
||||
self.permissions.push(p.code)
|
||||
}
|
||||
})
|
||||
self.isLoading = false
|
||||
})
|
||||
},
|
||||
update (attr) {
|
||||
let newValue = this.object[attr]
|
||||
let params = {}
|
||||
if (attr === 'permissions') {
|
||||
params['permissions'] = {}
|
||||
this.allPermissions.forEach(p => {
|
||||
params['permissions'][p.code] = this.permissions.indexOf(p.code) > -1
|
||||
})
|
||||
} else {
|
||||
params[attr] = newValue
|
||||
}
|
||||
axios.patch('manage/users/users/' + this.id + '/', params).then((response) => {
|
||||
logger.default.info(`${attr} was updated succcessfully to ${newValue}`)
|
||||
}, (error) => {
|
||||
logger.default.error(`Error while setting ${attr} to ${newValue}`, error)
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allPermissions () {
|
||||
return [
|
||||
{
|
||||
'code': 'upload',
|
||||
'label': this.$t('Upload')
|
||||
},
|
||||
{
|
||||
'code': 'library',
|
||||
'label': this.$t('Library')
|
||||
},
|
||||
{
|
||||
'code': 'federation',
|
||||
'label': this.$t('Federation')
|
||||
},
|
||||
{
|
||||
'code': 'settings',
|
||||
'label': this.$t('Settings')
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
object () {
|
||||
this.$nextTick(() => {
|
||||
$('select.dropdown').dropdown()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
</style>
|
Ładowanie…
Reference in New Issue