It was broken because "organizations" was erronously included in the `search_fields` admin option. Many-to-many fields may not be used for search. TNL-8722
207 lines
7.1 KiB
Python
207 lines
7.1 KiB
Python
"""
|
|
django admin page for the course creators table
|
|
"""
|
|
|
|
|
|
import logging
|
|
from smtplib import SMTPException
|
|
|
|
from django import forms
|
|
from django.conf import settings
|
|
from django.contrib import admin
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.mail import send_mail
|
|
from django.db.models.signals import m2m_changed
|
|
from django.dispatch import receiver
|
|
|
|
from cms.djangoapps.course_creators.models import (
|
|
CourseCreator,
|
|
send_admin_notification,
|
|
send_user_notification,
|
|
update_creator_state
|
|
)
|
|
from cms.djangoapps.course_creators.views import update_course_creator_group, update_org_content_creator_role
|
|
from common.djangoapps.edxmako.shortcuts import render_to_string
|
|
|
|
log = logging.getLogger("studio.coursecreatoradmin")
|
|
|
|
|
|
def get_email(obj):
|
|
""" Returns the email address for a user """
|
|
return obj.user.email
|
|
|
|
get_email.short_description = 'email'
|
|
|
|
|
|
class CourseCreatorForm(forms.ModelForm):
|
|
"""
|
|
Admin form for course creator
|
|
"""
|
|
class Meta:
|
|
model = CourseCreator
|
|
fields = '__all__'
|
|
|
|
def clean(self):
|
|
"""
|
|
Validate the 'state', 'organizations' and 'all_orgs' field before saving.
|
|
"""
|
|
all_orgs = self.cleaned_data.get("all_organizations")
|
|
orgs = self.cleaned_data.get("organizations").exists()
|
|
state = self.cleaned_data.get("state")
|
|
is_all_org_selected_with_orgs = (orgs and all_orgs)
|
|
is_orgs_added_with_all_orgs_selected = (not orgs and not all_orgs)
|
|
is_state_granted = state == CourseCreator.GRANTED
|
|
if is_state_granted:
|
|
if is_all_org_selected_with_orgs:
|
|
raise ValidationError(
|
|
"The role can be granted either to ALL organizations or to "
|
|
"specific organizations but not both."
|
|
)
|
|
if is_orgs_added_with_all_orgs_selected:
|
|
raise ValidationError(
|
|
"Specific organizations needs to be selected to grant this role,"
|
|
"if it is not granted to all organiztions"
|
|
)
|
|
|
|
|
|
class CourseCreatorAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin for the course creator table.
|
|
"""
|
|
|
|
# Fields to display on the overview page.
|
|
list_display = ['username', get_email, 'state', 'state_changed', 'note', 'all_organizations']
|
|
filter_horizontal = ('organizations',)
|
|
readonly_fields = ['username', 'state_changed']
|
|
# Controls the order on the edit form (without this, read-only fields appear at the end).
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ['username', 'state', 'state_changed', 'note', 'all_organizations', 'organizations']
|
|
}),
|
|
)
|
|
# Fields that filtering support
|
|
list_filter = ['state', 'state_changed']
|
|
# Fields that search supports.
|
|
search_fields = ['user__username', 'user__email', 'state', 'note']
|
|
# Turn off the action bar (we have no bulk actions)
|
|
actions = None
|
|
form = CourseCreatorForm
|
|
|
|
def username(self, inst):
|
|
"""
|
|
Returns the username for a given user.
|
|
|
|
Implemented to make sorting by username instead of by user object.
|
|
"""
|
|
return inst.user.username
|
|
|
|
username.admin_order_field = 'user__username'
|
|
|
|
def has_add_permission(self, request):
|
|
return False
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
return False
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
return request.user.is_staff
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
# Store who is making the request.
|
|
obj.admin = request.user
|
|
obj.save()
|
|
|
|
# This functions is overriden to update the m2m query
|
|
def save_related(self, request, form, formsets, change):
|
|
super().save_related(request, form, formsets, change)
|
|
state = form.instance.state
|
|
if state != CourseCreator.GRANTED:
|
|
form.instance.organizations.clear()
|
|
|
|
|
|
admin.site.register(CourseCreator, CourseCreatorAdmin)
|
|
|
|
|
|
@receiver(update_creator_state, sender=CourseCreator)
|
|
def update_creator_group_callback(sender, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Callback for when the model's creator status has changed.
|
|
"""
|
|
user = kwargs['user']
|
|
updated_state = kwargs['state']
|
|
all_orgs = kwargs['all_organizations']
|
|
create_role = all_orgs and (updated_state == CourseCreator.GRANTED)
|
|
update_course_creator_group(kwargs['caller'], user, create_role)
|
|
|
|
|
|
@receiver(send_user_notification, sender=CourseCreator)
|
|
def send_user_notification_callback(sender, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Callback for notifying user about course creator status change.
|
|
"""
|
|
user = kwargs['user']
|
|
updated_state = kwargs['state']
|
|
|
|
studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '')
|
|
context = {'studio_request_email': studio_request_email}
|
|
|
|
subject = render_to_string('emails/course_creator_subject.txt', context)
|
|
subject = ''.join(subject.splitlines())
|
|
if updated_state == CourseCreator.GRANTED:
|
|
message_template = 'emails/course_creator_granted.txt'
|
|
elif updated_state == CourseCreator.DENIED:
|
|
message_template = 'emails/course_creator_denied.txt'
|
|
else:
|
|
# changed to unrequested or pending
|
|
message_template = 'emails/course_creator_revoked.txt'
|
|
message = render_to_string(message_template, context)
|
|
|
|
try:
|
|
user.email_user(subject, message, studio_request_email)
|
|
except: # lint-amnesty, pylint: disable=bare-except
|
|
log.warning("Unable to send course creator status e-mail to %s", user.email)
|
|
|
|
|
|
@receiver(send_admin_notification, sender=CourseCreator)
|
|
def send_admin_notification_callback(sender, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Callback for notifying admin of a user in the 'pending' state.
|
|
"""
|
|
user = kwargs['user']
|
|
|
|
studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '')
|
|
context = {'user_name': user.username, 'user_email': user.email}
|
|
|
|
subject = render_to_string('emails/course_creator_admin_subject.txt', context)
|
|
subject = ''.join(subject.splitlines())
|
|
message = render_to_string('emails/course_creator_admin_user_pending.txt', context)
|
|
|
|
try:
|
|
send_mail(
|
|
subject,
|
|
message,
|
|
studio_request_email,
|
|
[studio_request_email],
|
|
fail_silently=False
|
|
)
|
|
except SMTPException:
|
|
log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email)
|
|
|
|
|
|
@receiver(m2m_changed, sender=CourseCreator.organizations.through)
|
|
def course_creator_organizations_changed_callback(sender, **kwargs): # pylint: disable=unused-argument
|
|
"""
|
|
Callback for addition and removal of orgs field.
|
|
"""
|
|
instance = kwargs["instance"]
|
|
action = kwargs["action"]
|
|
orgs = list(instance.organizations.all().values_list('short_name', flat=True))
|
|
updated_state = instance.state
|
|
is_granted = updated_state == CourseCreator.GRANTED
|
|
should_update_role = (
|
|
(action in ["post_add", "post_remove"] and is_granted) or
|
|
(action == "post_clear" and not is_granted)
|
|
)
|
|
if should_update_role:
|
|
update_org_content_creator_role(instance.admin, instance.user, orgs)
|