blender-studio/users/admin.py

182 lines
6.3 KiB
Python

from django.contrib import admin
from django.contrib.auth import get_user_model, admin as auth_admin
from django.db.models import Count, Q
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from rest_framework.authtoken.admin import TokenAdmin
import nested_admin
from looper.admin import CUSTOMER_SEARCH_FIELDS
import looper.admin
import looper.models
from users.models import Notification
from training.models import progress
TokenAdmin.raw_id_fields = ['user']
REL_CUSTOMER_SEARCH_FIELDS = [f'customer__{_}' for _ in CUSTOMER_SEARCH_FIELDS]
def user_section_progress_link(obj):
admin_view = looper.admin._get_admin_url_name(progress.UserSectionProgress, 'changelist')
link = reverse(admin_view) + f'?user_id={obj.pk}'
return format_html('<a href="{}">{}</a>', link, 'View training progress for this user')
user_section_progress_link.short_description = 'Training sections progress'
def user_subscriptions_link(obj, title='View subscriptions of this user'):
admin_view = looper.admin._get_admin_url_name(looper.models.Subscription, 'changelist')
link = reverse(admin_view) + f'?customer_id={obj.customer.pk}'
return format_html('<a href="{}">{}</a>', link, title)
user_subscriptions_link.short_description = 'Subscriptions'
class NumberOfSubscriptionsFilter(admin.SimpleListFilter):
title = _('subscriptions')
parameter_name = 'subscriptions_count'
def lookups(self, request, model_admin):
"""Human-readable labels for filter choices."""
return (
('none', 'without subscriptions'),
('one', 'with one subscription'),
('multiple', 'with more than one subscription'),
)
def queryset(self, request, queryset):
"""Returns the filtered queryset based on the value provided in the query string."""
if self.value() == 'none':
return queryset.filter(subscriptions_count=0)
if self.value() == 'one':
return queryset.filter(subscriptions_count=1)
if self.value() == 'multiple':
return queryset.filter(subscriptions_count__gt=1)
class NumberOfBraintreeCustomerIDsFilter(admin.SimpleListFilter):
title = _('Braintree Customer IDs')
parameter_name = 'braintreecustomerids_count'
def lookups(self, request, model_admin):
"""Human-readable labels for filter choices."""
return (
('none', 'none'),
('one', 'one'),
('multiple', 'multiple'),
)
def queryset(self, request, queryset):
"""Returns the filtered queryset based on the value provided in the query string."""
if self.value() == 'none':
return queryset.filter(braintreecustomerids_count=0)
if self.value() == 'one':
return queryset.filter(braintreecustomerids_count=1)
if self.value() == 'multiple':
return queryset.filter(braintreecustomerids_count__gt=1)
@admin.register(get_user_model())
class UserAdmin(auth_admin.UserAdmin, nested_admin.NestedModelAdmin):
change_form_template = 'loginas/change_form.html'
def has_add_permission(self, request):
"""User records are managed by Blender ID, so no new user should be added here."""
return False
def get_queryset(self, *args, **kwargs):
"""Count user subscriptions for subscription debugging purposes."""
queryset = super().get_queryset(*args, **kwargs).prefetch_related('customer')
queryset = queryset.annotate(
subscriptions_count=Count('customer__subscription', distinct=True),
braintreecustomerids_count=Count(
'customer__gatewaycustomerid', Q(customer__gateway__name='braintree'), distinct=True
),
)
return queryset
def subscriptions(self, obj):
"""Return the number of subscriptions this user has and a link to them."""
return user_subscriptions_link(obj, obj.subscriptions_count)
list_display_links = ['username']
list_filter = auth_admin.UserAdmin.list_filter + (
'date_joined',
'is_subscribed_to_newsletter',
'confirmed_email_at',
'date_deletion_requested',
'last_login',
NumberOfSubscriptionsFilter,
NumberOfBraintreeCustomerIDsFilter,
)
list_display = [
_ for _ in auth_admin.UserAdmin.list_display if _ not in ('first_name', 'last_name')
] + ['is_active', 'email_confirmed', 'deletion_requested', 'subscriptions']
fieldsets = (
(None, {'fields': ('username', 'password')}),
(
_('Personal info'),
{'fields': ('full_name', 'image', 'email', 'is_subscribed_to_newsletter', 'badges')},
),
(
_('Permissions'),
{'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')},
),
(
_('Important dates'),
{
'fields': (
('date_joined', 'last_login'),
'confirmed_email_at',
'date_deletion_requested',
)
},
),
(_('Activity'), {'fields': (user_section_progress_link, user_subscriptions_link)}),
)
readonly_fields = (
'confirmed_email_at',
'date_deletion_requested',
'date_joined',
'last_login',
user_section_progress_link,
user_subscriptions_link,
subscriptions,
)
inlines = (
looper.admin.LinkCustomerTokenInline,
looper.admin.CustomerInline,
)
ordering = ['-date_joined']
search_fields = ['email', 'full_name', 'username', *REL_CUSTOMER_SEARCH_FIELDS]
def deletion_requested(self, obj):
"""Display yes/no icon status of deletion request."""
return obj.date_deletion_requested is not None
deletion_requested.boolean = True
def email_confirmed(self, obj):
"""Display yes/no icon status of email confirmation."""
return obj.confirmed_email_at is not None
email_confirmed.boolean = True
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
"""Configure Notification admin."""
search_fields = ['user__username', 'user__email', 'user__profile__full_name']
list_display = ['__str__', 'user', 'action']
raw_id_fields = ['user', 'action']
list_filter = ['action__verb']