Commit 682e7dd2 by tasawernawaz Committed by Tasawer Nawaz

view and edit new staff member added on course run page

ECOM-7559
parent 7b7ca892
......@@ -257,6 +257,34 @@ class PersonSerializer(serializers.ModelSerializer):
return person
def update(self, instance, validated_data):
position_data = validated_data.pop('position')
works_data = validated_data.pop('works', [])
urls_data = validated_data.pop('urls', {})
instance.position.title = position_data['title']
instance.position.organization = position_data['organization']
instance.position.save()
for url_type in [PersonSocialNetwork.FACEBOOK, PersonSocialNetwork.TWITTER, PersonSocialNetwork.BLOG]:
value = urls_data.get(url_type)
if value:
network, __ = PersonSocialNetwork.objects.get_or_create(person=instance, type=url_type)
network.value = value
network.save()
else:
PersonSocialNetwork.objects.filter(person=instance, type=url_type).delete()
instance.person_works.exclude(value__in=works_data).delete()
for work in works_data:
PersonWork.objects.get_or_create(person=instance, value=work)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
def get_social_network_url(self, url_type, obj):
# filter() isn't used to avoid discarding prefetched results.
social_networks = [network for network in obj.person_networks.all() if network.type == url_type]
......
# pylint: disable=redefined-builtin,no-member
import json
import ddt
from django.conf import settings
from django.contrib.auth import get_user_model
......@@ -16,7 +14,8 @@ from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.course_metadata.models import Person
from course_discovery.apps.course_metadata.people import MarketingSitePeople
from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.course_metadata.tests.factories import OrganizationFactory, PartnerFactory, PersonFactory
from course_discovery.apps.course_metadata.tests.factories import (OrganizationFactory, PartnerFactory, PersonFactory,
PositionFactory)
User = get_user_model()
......@@ -31,6 +30,7 @@ class PersonViewSetTests(SerializationMixin, APITestCase):
self.user = UserFactory(is_staff=True, is_superuser=True)
self.client.force_authenticate(self.user)
self.person = PersonFactory()
PositionFactory(person=self.person)
self.organization = OrganizationFactory()
# DEFAULT_PARTNER_ID is used explicitly here to avoid issues with differences in
# auto-incrementing behavior across databases. Otherwise, it's not safe to assume
......@@ -50,7 +50,7 @@ class PersonViewSetTests(SerializationMixin, APITestCase):
response = self.client.post(self.people_list_url, self._person_data(), format='json')
self.assertEqual(response.status_code, 201)
data = json.loads(self._person_data()['data'])
data = self._person_data()
person = Person.objects.last()
self.assertDictEqual(response.data, self.serialize_person(person))
self.assertEqual(person.given_name, data['given_name'])
......@@ -68,7 +68,7 @@ class PersonViewSetTests(SerializationMixin, APITestCase):
""" Verify that if credentials are missing api will return the error. """
self.partner.marketing_site_api_username = None
self.partner.save()
data = json.loads(self._person_data()['data'])
data = self._person_data()
with LogCapture(people_logger.name) as log_capture:
response = self.client.post(self.people_list_url, self._person_data(), format='json')
......@@ -87,7 +87,7 @@ class PersonViewSetTests(SerializationMixin, APITestCase):
""" Verify that after creating drupal page if serializer fail due to any error, message
will be logged and drupal page will be deleted. """
data = json.loads(self._person_data()['data'])
data = self._person_data()
with mock.patch.object(MarketingSitePeople, 'publish_person', return_value=self.expected_node):
with mock.patch(
'course_discovery.apps.api.v1.views.people.PersonViewSet.perform_create',
......@@ -137,23 +137,52 @@ class PersonViewSetTests(SerializationMixin, APITestCase):
self.assertEqual(response.status_code, 400)
def _person_data(self):
return {
'data': json.dumps(
{
'given_name': "Robert",
'family_name': "Ford",
'bio': "The maze is not for him.",
'position': {
'title': "Park Director",
'organization': self.organization.id
},
'works': ["Delores", "Teddy", "Maive"],
'urls': {
'facebook': 'http://www.facebook.com/hopkins',
'twitter': 'http://www.twitter.com/hopkins',
'blog': 'http://www.blog.com/hopkins'
}
}
)
'given_name': "Robert",
'family_name': "Ford",
'bio': "The maze is not for him.",
'position': {
'title': "Park Director",
'organization': self.organization.id
},
'works': ["Delores", "Teddy", "Maive"],
'urls': {
'facebook': 'http://www.facebook.com/hopkins',
'twitter': 'http://www.twitter.com/hopkins',
'blog': 'http://www.blog.com/hopkins'
}
}
def test_update(self):
"""Verify that people data can be updated using endpoint."""
url = reverse('api:v1:person-detail', kwargs={'uuid': self.person.uuid})
data = {
'given_name': "updated",
'family_name': "name",
'bio': "updated bio",
'position': {
'title': "new title",
'organization': self.organization.id
},
'works': ["new", "added"],
'urls': {
'facebook': 'http://www.facebook.com/new',
'twitter': 'http://www.twitter.com/new',
}
}
response = self.client.patch(url, data, format='json')
self.assertEqual(response.status_code, 200)
updated_person = Person.objects.get(id=self.person.id)
self.assertEqual(updated_person.given_name, data['given_name'])
self.assertEqual(updated_person.family_name, data['family_name'])
self.assertEqual(updated_person.bio, data['bio'])
self.assertEqual(updated_person.position.title, data['position']['title'])
self.assertEqual(updated_person.person_works.all()[0].value, data['works'][0])
self.assertEqual(updated_person.person_works.all()[1].value, data['works'][1])
self.assertEqual(updated_person.person_networks.get(type='facebook').value, data['urls']['facebook'])
self.assertEqual(updated_person.person_networks.get(type='twitter').value, data['urls']['twitter'])
self.assertFalse(updated_person.person_networks.filter(type='blog').exists())
import json
import logging
import waffle
......@@ -29,7 +28,7 @@ class PersonViewSet(PartnerMixin, viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
""" Create a new person. """
person_data = json.loads(request.data.get('data'))
person_data = request.data
partner = self.get_partner()
person_data['partner'] = partner.id
......
......@@ -27,7 +27,8 @@ class PersonModelMultipleChoice(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
context = {
'profile_image': obj.get_profile_image_url,
'full_name': obj.full_name
'full_name': obj.full_name,
'uuid': obj.uuid if not obj.profile_image_url else None
}
return str(render_to_string('publisher/_personFieldLabel.html', context=context))
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-30 14:55+0500\n"
"POT-Creation-Date: 2017-03-31 11:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py
#, python-brace-format
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-30 14:55+0500\n"
"POT-Creation-Date: 2017-03-31 11:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js
msgid "Preview"
......@@ -37,6 +37,14 @@ msgid "The image file size cannot exceed 1 MB."
msgstr ""
#: static/js/publisher/instructors.js
msgid "New Instructor"
msgstr ""
#: static/js/publisher/instructors.js
msgid "Add staff member"
msgstr ""
#: static/js/publisher/instructors.js
msgid ""
"Please upload a instructor image. File must be smaller than 1 megabyte in "
"size."
......@@ -50,6 +58,14 @@ msgstr ""
msgid "File must be smaller than 1 megabyte in size."
msgstr ""
#: static/js/publisher/instructors.js
msgid "Update staff member"
msgstr ""
#: static/js/publisher/instructors.js
msgid "Update Instructor"
msgstr ""
#: static/js/publisher/preview-url.js
msgid "Save"
msgstr ""
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-30 14:55+0500\n"
"POT-Creation-Date: 2017-03-31 11:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-30 14:55+0500\n"
"POT-Creation-Date: 2017-03-31 11:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js
......@@ -42,6 +42,14 @@ msgstr ""
"¢σηѕє¢тєтυя#"
#: static/js/publisher/instructors.js
msgid "New Instructor"
msgstr "Néw Ìnstrüçtör Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
#: static/js/publisher/instructors.js
msgid "Add staff member"
msgstr "Àdd stäff mémßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
#: static/js/publisher/instructors.js
msgid ""
"Please upload a instructor image. File must be smaller than 1 megabyte in "
"size."
......@@ -59,6 +67,14 @@ msgstr ""
"Fïlé müst ßé smällér thän 1 mégäßýté ïn sïzé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя #"
#: static/js/publisher/instructors.js
msgid "Update staff member"
msgstr "Ûpdäté stäff mémßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#"
#: static/js/publisher/instructors.js
msgid "Update Instructor"
msgstr "Ûpdäté Ìnstrüçtör Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмє#"
#: static/js/publisher/preview-url.js
msgid "Save"
msgstr "Sävé Ⱡ'σяєм ι#"
......
......@@ -5,58 +5,83 @@ $(document).ready(function(){
label = $.parseHTML(this.label),
image_source = $(label[0]).attr('src'),
name = $(label[1]).text();
renderSelectedInstructor(id, name, image_source);
uuid = $(label[1]).data('uuid');
renderSelectedInstructor(id, name, image_source, uuid);
});
$('#add-new-instructor').click(function(e){
clearModalError();
var btnInstructor = $('#add-instructor-btn');
$('#addInstructorModal').show();
$('body').addClass('stopScroll');
$('.new-instructor-heading').text(gettext('New Instructor'));
btnInstructor.removeClass('edit-mode');
btnInstructor.text(gettext('Add staff member'));
});
$('#add-instructor-btn').click(function (e) {
if ($('#staffImageSelect').get(0).files.length === 0){
addModalError(gettext("Please upload a instructor image. File must be smaller than 1 megabyte in size."));
return false;
}
var editMode = $(this).hasClass('edit-mode'),
requestType,
personData,
url = $(this).data('url'),
uuid = $('#addInstructorModal').data('uuid');
personData = {
'given_name': $('#given-name').val(),
'family_name': $('#family-name').val(),
'bio': $('#bio').val(),
'profile_image': $('.select-image').attr('src'),
'position': {
title: $('#title').val(),
organization: parseInt($('#id_organization').val())
},
'works': $('#majorWorks').val().split('\n'),
'urls': {
facebook: $('#facebook').val(),
twitter: $('#twitter').val(),
blog: $('#blog').val()
}
};
if (editMode) {
requestType = "PATCH";
personData['uuid'] = uuid;
url = url + uuid + '/';
if (!$('.select-image').hasClass('image-updated')) {
delete personData['profile_image'];
}
} else {
requestType = "POST";
}
$.ajax({
type: "POST",
url: $(this).data('url'),
data: {
'data': JSON.stringify(
{
'given_name': $('#given-name').val(),
'family_name': $('#family-name').val(),
'bio': $('#bio').val(),
'profile_image': $('.select-image').attr('src'),
'position':{
'title': $('#title').val(),
'organization': parseInt($('#id_organization').val())
},
'works': $('#majorWorks').val().split('\n'),
'urls': {
'facebook': $('#facebook').val(),
'twitter': $('#twitter').val(),
'blog': $('#blog').val()
}
}
)
},
type: requestType,
url: url,
contentType: "application/json",
data: JSON.stringify(personData),
success: function (response) {
$('#given-name').val('');
$('#family-name').val('');
$('#title').val('');
$('#bio').val('');
$('.select-image').attr('src', '');
$('.select-image').attr('src', '').removeClass('image-updated');
$('#majorWorks').val('');
$('#facebook').val('');
$('#twitter').val('');
$('#blog').val('');
clearModalError();
closeModal(e, $('#addInstructorModal'));
loadInstructor(response['uuid'])
if (editMode) {
loadInstructor(response['uuid'], editMode)
} else {
loadInstructor(response['uuid'])
}
},
error: function (response) {
addModalError(gettext("Something went wrong!"));
......@@ -78,7 +103,7 @@ function loadSelectedImage(input) {
clearModalError();
reader.onload = function (e) {
$('.select-image').attr('src', e.target.result);
$('.select-image').attr('src', e.target.result).addClass('image-updated');
};
reader.readAsDataURL(input.files[0]);
......@@ -101,7 +126,7 @@ $(document).on('change', '#id_staff', function (e) {
});
$(document).on('click', '.selected-instructor a', function (e) {
$(document).on('click', '.selected-instructor a.delete', function (e) {
e.preventDefault();
var id = this.id,
option = $('#id_staff').find('option[value="' + id + '"]');
......@@ -110,11 +135,16 @@ $(document).on('click', '.selected-instructor a', function (e) {
this.closest('.selected-instructor, .instructor').remove();
});
function renderSelectedInstructor(id, name, image) {
var instructorHtml = '<div class="instructor"><div><img src="' + image + '"></div><div><a id="' + id + '" ' +
'href="#"><i class="fa fa-trash-o fa-fw"></i></a><b>' + name + '</b></div></div>';
function renderSelectedInstructor(id, name, image, uuid) {
var instructorHtmlStart = '<div class="instructor"><div><img src="' + image + '"></div><div>',
instructorHtmlEnd = '<b>' + name + '</b></div></div>',
controlOptions = '<a class="delete" id="' + id + '"href="#"><i class="fa fa-trash-o fa-fw"></i></a>';
if (uuid) {
controlOptions += '<a class="edit" id="' + uuid + '"href="#"><i class="fa fa-pencil-square-o fa-fw"></i></a>';
}
$('.selected-instructor').append(instructorHtml);
$('.selected-instructor').append(instructorHtmlStart + controlOptions + instructorHtmlEnd);
}
$(document).on('click', '.btn-save-preview-url', function (e) {
......@@ -137,7 +167,7 @@ $(document).on('click', '.btn-save-preview-url', function (e) {
});
});
function loadInstructor(uuid) {
function loadInstructor(uuid, editMode) {
var url = $('#id_staff').attr('data-autocomplete-light-url') + '?q=' + uuid,
instructor,
id,
......@@ -159,9 +189,59 @@ function loadInstructor(uuid) {
value: id,
text: name
}).attr('selected', 'selected'));
renderSelectedInstructor(id, name, image_source);
if (editMode) {
//before loading updated instructor it will remove old one.
$('#' + id).click();
}
renderSelectedInstructor(id, name, image_source, uuid);
}
}
});
}
$(document).on('click', '.selected-instructor a.edit', function (e) {
e.preventDefault();
var uuid = this.id,
btnInstructor = $('#add-instructor-btn'),
instructorModal = $('#addInstructorModal');
$('body').addClass('stopScroll');
instructorModal.show();
instructorModal.data('uuid', uuid);
btnInstructor.addClass('edit-mode');
btnInstructor.text(gettext('Update staff member'));
$('.new-instructor-heading').text(gettext('Update Instructor'));
$.getJSON({
url: btnInstructor.data('url') + uuid,
success: function (data) {
convertFileToDataURL(data['profile_image']['medium']['url'], function(base64Img) {
$('.select-image').attr('src', base64Img);
});
$('#given-name').val(data['given_name']);
$('#family-name').val(data['family_name']);
$('#title').val(data['position']['title']);
$('#bio').val(data['bio']);
$('#majorWorks').val(data['works'].join('\n'));
$('#facebook').val(data['urls']['facebook']);
$('#twitter').val(data['urls']['twitter']);
$('#blog').val(data['urls']['blog']);
}
});
});
function convertFileToDataURL(url, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var reader = new FileReader();
reader.onloadend = function() {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
}
......@@ -516,9 +516,12 @@
div {
text-align: center;
}
a {
a.delete {
color: red;
}
a.edit {
color: #000000;
}
}
.instructor-select {
......
<img src="{{ profile_image }}"/><span>{{ full_name }}</span>
<img src="{{ profile_image }}"/><span{% if uuid %} data-uuid="{{ uuid }}" {% endif %}>{{ full_name }}</span>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment