@@ -0,0 +1,21 @@
|
||||
{% extends 'admin/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div>
|
||||
<h2>Generate External IDs in bulk</h2>
|
||||
<P>
|
||||
This form will generate external IDs for a set of Users based on the
|
||||
provided CSV file. The file should contain a header labeled "ID" and then
|
||||
a list of numeric IDs, one row per ID.
|
||||
</P>
|
||||
<form action="." method="POST" enctype="multipart/form-data">
|
||||
{{ form.as_p }}
|
||||
{% csrf_token %}
|
||||
|
||||
<a href="/admin/external_user_ids/externalid/">Cancel</a>
|
||||
<button type="submit">Upload CSV</button>
|
||||
</form>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends 'admin/change_list.html' %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
<li><a href="bulk_generate_external_ids/">Bulk Generate External IDs</a></li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
129
openedx/core/djangoapps/external_user_ids/admin.py
Normal file
129
openedx/core/djangoapps/external_user_ids/admin.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import csv
|
||||
from logging import getLogger
|
||||
|
||||
from django import forms
|
||||
from django.conf.urls import url
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import ExternalId, ExternalIdType
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class CsvImportForm(forms.Form):
|
||||
csv_file = forms.FileField(label='CSV File')
|
||||
id_type = forms.ModelChoiceField(
|
||||
label='External ID Type',
|
||||
queryset=ExternalIdType.objects.all()
|
||||
)
|
||||
|
||||
|
||||
@admin.register(ExternalId)
|
||||
class ExternalIdAdmin(admin.ModelAdmin):
|
||||
change_list_template = 'admin/external_user_ids/generate_external_user_ids.html'
|
||||
list_display = ('user', 'external_user_id', 'external_id_type')
|
||||
template = 'openedx/core/djangoapps/external_user_ids/templates/admin/generate_external_ids_template.html'
|
||||
|
||||
def get_urls(self):
|
||||
urls = super(ExternalIdAdmin, self).get_urls()
|
||||
custom_urls = [
|
||||
url(
|
||||
r'^bulk_generate_external_ids/$',
|
||||
self.admin_site.admin_view(self.generate_ids_form),
|
||||
name='bulk_generate_external_ids'
|
||||
),
|
||||
]
|
||||
return custom_urls + urls
|
||||
|
||||
def _generate_results_msg(self, user_id_list, unknown_users, created_id_list, existing_id):
|
||||
return (
|
||||
'Attempted to create for: {}\n'.format(user_id_list) +
|
||||
'Could not find: {}\n'.format(unknown_users) +
|
||||
'Created External IDs for: {}\n'.format(created_id_list) +
|
||||
'External IDs already exist for: {}\n'.format(existing_id)
|
||||
)
|
||||
|
||||
def process_generate_ids_request(self, user_id_list, id_type, request, redirect_url):
|
||||
created_id_list = []
|
||||
existing_id = []
|
||||
|
||||
user_list = User.objects.filter(
|
||||
id__in=user_id_list
|
||||
)
|
||||
for user in user_list:
|
||||
new_external_id, created = ExternalId.objects.get_or_create(
|
||||
user=user,
|
||||
external_id_type=id_type,
|
||||
)
|
||||
if created:
|
||||
created_id_list.append(user.id)
|
||||
else:
|
||||
existing_id.append(user.id)
|
||||
found_user_ids = created_id_list + existing_id
|
||||
unknown_users = list(set(user_id_list) - set(found_user_ids))
|
||||
result_msg = self._generate_results_msg(user_id_list, unknown_users, created_id_list, existing_id)
|
||||
logger.info(result_msg)
|
||||
self.message_user(
|
||||
request,
|
||||
result_msg,
|
||||
level=messages.SUCCESS)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
def _render_form(self, request, form):
|
||||
context = {
|
||||
'form': form
|
||||
}
|
||||
return render(
|
||||
request,
|
||||
'admin/external_user_ids/generate_external_ids_form.html',
|
||||
context
|
||||
)
|
||||
|
||||
def generate_ids_form(self, request):
|
||||
if request.method == 'POST':
|
||||
redirect_url = reverse(
|
||||
'admin:external_user_ids_externalid_changelist',
|
||||
current_app=self.admin_site.name,
|
||||
)
|
||||
upload_file = request.FILES.get('csv_file')
|
||||
id_type = request.POST.get('id_type')
|
||||
|
||||
if not upload_file or not id_type:
|
||||
self.message_user(request, 'CSV file and type are required.', level=messages.ERROR)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
try:
|
||||
id_type = ExternalIdType.objects.get(id=id_type)
|
||||
except ExternalIdType.DoesNotExist:
|
||||
self.message_user(request, 'ID Type selected does not exist', level=messages.ERROR)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
reader = csv.reader(upload_file.read().decode('utf-8').splitlines())
|
||||
headers = next(reader, None)
|
||||
if len(headers) != 1 or 'ID' not in headers:
|
||||
self.message_user(
|
||||
request,
|
||||
'File is incorrectly formatted. To many columns or incorrectly named ID column',
|
||||
level=messages.ERROR
|
||||
)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
try:
|
||||
user_ids = [int(row[0]) for row in reader]
|
||||
except ValueError:
|
||||
self.message_user(
|
||||
request,
|
||||
'Data is incorrectly formatted. All ids must be integers',
|
||||
level=messages.ERROR
|
||||
)
|
||||
return HttpResponseRedirect(redirect_url)
|
||||
|
||||
return self.process_generate_ids_request(user_ids, id_type, request, redirect_url)
|
||||
|
||||
form = CsvImportForm()
|
||||
return self._render_form(request, form)
|
||||
@@ -11,7 +11,6 @@ from django.db import models
|
||||
from model_utils.models import TimeStampedModel
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
|
||||
LOGGER = getLogger(__name__)
|
||||
|
||||
|
||||
@@ -28,6 +27,9 @@ class ExternalIdType(TimeStampedModel):
|
||||
description = models.TextField()
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ExternalId(TimeStampedModel):
|
||||
"""
|
||||
|
||||
26
openedx/core/djangoapps/external_user_ids/tests/factories.py
Normal file
26
openedx/core/djangoapps/external_user_ids/tests/factories.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Test Factory classes for ExternalUserIds
|
||||
"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import factory
|
||||
from factory.fuzzy import FuzzyChoice, FuzzyText
|
||||
|
||||
from openedx.core.djangoapps.external_user_ids.models import ExternalId, ExternalIdType
|
||||
|
||||
|
||||
class ExternalIDTypeFactory(factory.django.DjangoModelFactory):
|
||||
class Meta(object):
|
||||
model = ExternalIdType
|
||||
|
||||
name = FuzzyChoice([ExternalIdType.MICROBACHELORS_COACHING])
|
||||
description = FuzzyText()
|
||||
|
||||
|
||||
class ExternalIdFactory(factory.django.DjangoModelFactory):
|
||||
class Meta(object):
|
||||
model = ExternalId
|
||||
|
||||
external_user_id = factory.LazyFunction(uuid4)
|
||||
external_id_type = factory.SubFactory(ExternalIDTypeFactory)
|
||||
@@ -0,0 +1,62 @@
|
||||
# """
|
||||
# Test the logic behind the Generate External IDs tools in Admin
|
||||
# """
|
||||
import mock
|
||||
from django.conf import settings
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.test import TestCase
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
|
||||
# external_ids is not in CMS' INSTALLED_APPS so these imports will error during test collection
|
||||
if settings.ROOT_URLCONF == 'lms.urls':
|
||||
from openedx.core.djangoapps.external_user_ids.models import (
|
||||
ExternalId,
|
||||
)
|
||||
from openedx.core.djangoapps.external_user_ids.tests.factories import ExternalIDTypeFactory
|
||||
from openedx.core.djangoapps.external_user_ids.admin import ExternalIdAdmin
|
||||
|
||||
|
||||
@skip_unless_lms
|
||||
class TestGenerateExternalIds(TestCase):
|
||||
"""
|
||||
Test generating ExternalIDs for Users.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestGenerateExternalIds, self).setUp()
|
||||
self.users = UserFactory.create_batch(10)
|
||||
self.user_id_list = [user.id for user in self.users]
|
||||
self.external_id_admin = ExternalIdAdmin(ExternalId, AdminSite())
|
||||
|
||||
def test_generate_ids_for_all_users(self):
|
||||
id_type = ExternalIDTypeFactory.create()
|
||||
request = mock.Mock()
|
||||
|
||||
assert ExternalId.objects.count() == 0
|
||||
self.external_id_admin.process_generate_ids_request(
|
||||
user_id_list=self.user_id_list,
|
||||
id_type=id_type,
|
||||
request=request,
|
||||
redirect_url=''
|
||||
)
|
||||
assert ExternalId.objects.count() == 10
|
||||
|
||||
def test_no_new_for_existing_users(self):
|
||||
id_type = ExternalIDTypeFactory.create()
|
||||
request = mock.Mock()
|
||||
|
||||
for user in self.users:
|
||||
ExternalId.objects.create(
|
||||
user=user,
|
||||
external_id_type=id_type
|
||||
)
|
||||
|
||||
assert ExternalId.objects.count() == 10
|
||||
self.external_id_admin.process_generate_ids_request(
|
||||
user_id_list=self.user_id_list,
|
||||
id_type=id_type,
|
||||
request=request,
|
||||
redirect_url=''
|
||||
)
|
||||
assert ExternalId.objects.count() == 10
|
||||
Reference in New Issue
Block a user