Commit 40439efd by Waheed Ahmed

Added Instructor email field and fixed organization.

ECOM-7706
parent 6318abab
...@@ -214,6 +214,7 @@ class PersonSerializer(serializers.ModelSerializer): ...@@ -214,6 +214,7 @@ class PersonSerializer(serializers.ModelSerializer):
profile_image = StdImageSerializerField(required=False) profile_image = StdImageSerializerField(required=False)
works = serializers.SlugRelatedField(many=True, read_only=True, slug_field='value', source='person_works') works = serializers.SlugRelatedField(many=True, read_only=True, slug_field='value', source='person_works')
urls = serializers.SerializerMethodField() urls = serializers.SerializerMethodField()
email = serializers.EmailField(required=True)
@classmethod @classmethod
def prefetch_queryset(cls): def prefetch_queryset(cls):
...@@ -225,7 +226,7 @@ class PersonSerializer(serializers.ModelSerializer): ...@@ -225,7 +226,7 @@ class PersonSerializer(serializers.ModelSerializer):
model = Person model = Person
fields = ( fields = (
'uuid', 'given_name', 'family_name', 'bio', 'profile_image_url', 'slug', 'position', 'profile_image', 'uuid', 'given_name', 'family_name', 'bio', 'profile_image_url', 'slug', 'position', 'profile_image',
'partner', 'works', 'urls' 'partner', 'works', 'urls', 'email'
) )
extra_kwargs = { extra_kwargs = {
'partner': {'write_only': True} 'partner': {'write_only': True}
......
...@@ -1075,6 +1075,7 @@ class PersonSerializerTests(TestCase): ...@@ -1075,6 +1075,7 @@ class PersonSerializerTests(TestCase):
'blog': None 'blog': None
}, },
'slug': person.slug, 'slug': person.slug,
'email': person.email,
} }
self.assertDictEqual(serializer.data, expected) self.assertDictEqual(serializer.data, expected)
......
...@@ -140,6 +140,7 @@ class PersonViewSetTests(SerializationMixin, APITestCase): ...@@ -140,6 +140,7 @@ class PersonViewSetTests(SerializationMixin, APITestCase):
return { return {
'given_name': "Robert", 'given_name': "Robert",
'family_name': "Ford", 'family_name': "Ford",
'email': "test@example.com",
'bio': "The maze is not for him.", 'bio': "The maze is not for him.",
'position': { 'position': {
'title': "Park Director", 'title': "Park Director",
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2017-04-27 11:49
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course_metadata', '0052_create_course_run_publication_switch'),
]
operations = [
migrations.AddField(
model_name='person',
name='email',
field=models.EmailField(blank=True, max_length=255, null=True),
),
]
...@@ -210,6 +210,7 @@ class Person(TimeStampedModel): ...@@ -210,6 +210,7 @@ class Person(TimeStampedModel):
) )
slug = AutoSlugField(populate_from=('given_name', 'family_name'), editable=True) slug = AutoSlugField(populate_from=('given_name', 'family_name'), editable=True)
profile_url = models.URLField(null=True, blank=True) profile_url = models.URLField(null=True, blank=True)
email = models.EmailField(null=True, blank=True, max_length=255)
class Meta: class Meta:
unique_together = ( unique_together = (
......
...@@ -13,7 +13,7 @@ from opaque_keys.edx.keys import CourseKey ...@@ -13,7 +13,7 @@ from opaque_keys.edx.keys import CourseKey
from course_discovery.apps.course_metadata.choices import CourseRunPacing from course_discovery.apps.course_metadata.choices import CourseRunPacing
from course_discovery.apps.course_metadata.models import LevelType, Organization, Person, Subject from course_discovery.apps.course_metadata.models import LevelType, Organization, Person, Subject
from course_discovery.apps.ietf_language_tags.models import LanguageTag from course_discovery.apps.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher.mixins import LanguageModelSelect2Multiple, check_roles_access from course_discovery.apps.publisher.mixins import LanguageModelSelect2Multiple, get_user_organizations
from course_discovery.apps.publisher.models import (Course, CourseRun, CourseUserRole, OrganizationExtension, from course_discovery.apps.publisher.models import (Course, CourseRun, CourseUserRole, OrganizationExtension,
OrganizationUserRole, PublisherUser, Seat, User) OrganizationUserRole, PublisherUser, Seat, User)
from course_discovery.apps.publisher.utils import is_internal_user from course_discovery.apps.publisher.utils import is_internal_user
...@@ -181,17 +181,7 @@ class CustomCourseForm(CourseForm): ...@@ -181,17 +181,7 @@ class CustomCourseForm(CourseForm):
).order_by('full_name', 'username') ).order_by('full_name', 'username')
if user: if user:
organizations = Organization.objects.filter( self.declared_fields['organization'].queryset = get_user_organizations(user)
organization_extension__organization_id__isnull=False
).order_by(Lower('key'))
if not check_roles_access(user):
# If not internal user return only those organizations which belongs to user.
organizations = organizations.filter(
organization_extension__group__in=user.groups.all()
).order_by(Lower('key'))
self.declared_fields['organization'].queryset = organizations
self.declared_fields['team_admin'].widget.attrs = {'data-user': user.id} self.declared_fields['team_admin'].widget.attrs = {'data-user': user.id}
super(CustomCourseForm, self).__init__(*args, **kwargs) super(CustomCourseForm, self).__init__(*args, **kwargs)
......
...@@ -2,9 +2,11 @@ from functools import wraps ...@@ -2,9 +2,11 @@ from functools import wraps
from dal import autocomplete from dal import autocomplete
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models.functions import Lower
from django.http import HttpResponseForbidden, HttpResponseRedirect from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from course_discovery.apps.course_metadata.models import Organization
from course_discovery.apps.publisher.models import Course, Seat from course_discovery.apps.publisher.models import Course, Seat
from course_discovery.apps.publisher.utils import is_internal_user, is_publisher_admin, is_publisher_user from course_discovery.apps.publisher.utils import is_internal_user, is_publisher_admin, is_publisher_user
...@@ -131,3 +133,25 @@ class LanguageModelSelect2Multiple(autocomplete.ModelSelect2Multiple): ...@@ -131,3 +133,25 @@ class LanguageModelSelect2Multiple(autocomplete.ModelSelect2Multiple):
self.choices.queryset = self.choices.queryset.filter( self.choices.queryset = self.choices.queryset.filter(
code__in=[c for c in selected_choices if c] code__in=[c for c in selected_choices if c]
) )
def get_user_organizations(user):
"""
Get organizations for user.
Args:
user (Object): User object
Returns:
Organization (QuerySet): returns Organization objects queryset
"""
organizations = Organization.objects.filter(
organization_extension__organization_id__isnull=False
).order_by(Lower('key'))
if not check_roles_access(user):
# If not internal user return only those organizations which belongs to user.
organizations = organizations.filter(
organization_extension__group__in=user.groups.all()
).order_by(Lower('key'))
return organizations
...@@ -200,6 +200,7 @@ class CourseRunWrapperTests(TestCase): ...@@ -200,6 +200,7 @@ class CourseRunWrapperTests(TestCase):
'social_networks': {}, 'social_networks': {},
'bio': staff.bio, 'bio': staff.bio,
'is_new': True, 'is_new': True,
'email': staff.email
}, },
{ {
'uuid': str(staff_2.uuid), 'uuid': str(staff_2.uuid),
...@@ -210,7 +211,8 @@ class CourseRunWrapperTests(TestCase): ...@@ -210,7 +211,8 @@ class CourseRunWrapperTests(TestCase):
'profile_url': staff.profile_url, 'profile_url': staff.profile_url,
'is_new': False, 'is_new': False,
'social_networks': {'facebook': facebook.value, 'twitter': twitter.value}, 'social_networks': {'facebook': facebook.value, 'twitter': twitter.value},
'bio': staff_2.bio 'bio': staff_2.bio,
'email': staff_2.email
} }
] ]
......
""" Publisher Utils.""" """ Publisher Utils."""
from dateutil import parser from dateutil import parser
from course_discovery.apps.core.models import User from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.constants import (ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME, from course_discovery.apps.publisher.constants import (ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME,
PROJECT_COORDINATOR_GROUP_NAME) PROJECT_COORDINATOR_GROUP_NAME)
......
...@@ -594,12 +594,14 @@ class CourseRunEditView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMix ...@@ -594,12 +594,14 @@ class CourseRunEditView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMix
return reverse(self.success_url, kwargs={'pk': self.object.id}) return reverse(self.success_url, kwargs={'pk': self.object.id})
def get_context_data(self): def get_context_data(self):
user = self.request.user
return { return {
'course_run': self.get_object(), 'course_run': self.get_object(),
'publisher_hide_features_for_pilot': waffle.switch_is_active('publisher_hide_features_for_pilot'), 'publisher_hide_features_for_pilot': waffle.switch_is_active('publisher_hide_features_for_pilot'),
'publisher_add_instructor_feature': waffle.switch_is_active('publisher_add_instructor_feature'), 'publisher_add_instructor_feature': waffle.switch_is_active('publisher_add_instructor_feature'),
'is_internal_user': mixins.check_roles_access(self.request.user), 'is_internal_user': mixins.check_roles_access(user),
'is_project_coordinator': is_project_coordinator_user(self.request.user), 'is_project_coordinator': is_project_coordinator_user(user),
'organizations': mixins.get_user_organizations(user)
} }
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
......
...@@ -198,6 +198,7 @@ class CourseRunWrapper(BaseWrapper): ...@@ -198,6 +198,7 @@ class CourseRunWrapper(BaseWrapper):
'image_url': staff.get_profile_image_url, 'image_url': staff.get_profile_image_url,
'profile_url': staff.profile_url, 'profile_url': staff.profile_url,
'bio': staff.bio, 'bio': staff.bio,
'email': staff.email,
'social_networks': { 'social_networks': {
staff.type: staff.value staff.type: staff.value
for staff in staff.person_networks.all() for staff in staff.person_networks.all()
......
...@@ -1037,6 +1037,15 @@ msgstr "" ...@@ -1037,6 +1037,15 @@ msgstr ""
#: templates/publisher/_add_instructor_popup.html #: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html #: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Email"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
msgid "Institution email is preferred"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_studio.html #: templates/publisher/course_run_detail/_studio.html
#: templates/publisher/dashboard/_in_preview.html #: templates/publisher/dashboard/_in_preview.html
#: templates/publisher/dashboard/_in_progress.html #: templates/publisher/dashboard/_in_progress.html
......
...@@ -1197,6 +1197,15 @@ msgstr "Tïtlé Ⱡ'σяєм ιρѕ#" ...@@ -1197,6 +1197,15 @@ msgstr "Tïtlé Ⱡ'σяєм ιρѕ#"
#: templates/publisher/_add_instructor_popup.html #: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html #: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Email"
msgstr "Émäïl Ⱡ'σяєм ιρѕ#"
#: templates/publisher/_add_instructor_popup.html
msgid "Institution email is preferred"
msgstr "Ìnstïtütïön émäïl ïs préférréd Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_studio.html #: templates/publisher/course_run_detail/_studio.html
#: templates/publisher/dashboard/_in_preview.html #: templates/publisher/dashboard/_in_preview.html
#: templates/publisher/dashboard/_in_progress.html #: templates/publisher/dashboard/_in_progress.html
......
...@@ -34,6 +34,7 @@ $(document).ready(function(){ ...@@ -34,6 +34,7 @@ $(document).ready(function(){
'given_name': $('#given-name').val(), 'given_name': $('#given-name').val(),
'family_name': $('#family-name').val(), 'family_name': $('#family-name').val(),
'bio': $('#bio').val(), 'bio': $('#bio').val(),
'email': $('#email').val(),
'profile_image': $('.select-image').attr('src'), 'profile_image': $('.select-image').attr('src'),
'position': { 'position': {
title: $('#title').val(), title: $('#title').val(),
......
...@@ -39,6 +39,7 @@ $(document).ready(function(){ ...@@ -39,6 +39,7 @@ $(document).ready(function(){
$('#instructorProfileModal a.btn-download').attr('href', data['image_url']); $('#instructorProfileModal a.btn-download').attr('href', data['image_url']);
$('#instructorProfileModal div.position').html(data['position']); $('#instructorProfileModal div.position').html(data['position']);
$('#instructorProfileModal div.bio').html(data['bio']); $('#instructorProfileModal div.bio').html(data['bio']);
$('#instructorProfileModal div.email').html(data['email']);
assignData('.profile_url', data['profile_url']); assignData('.profile_url', data['profile_url']);
assignData('.facebook_url', data['social_networks']['facebook']); assignData('.facebook_url', data['social_networks']['facebook']);
......
...@@ -159,6 +159,14 @@ ...@@ -159,6 +159,14 @@
.actions { .actions {
@include text-align(right); @include text-align(right);
} }
#email {
margin-bottom: 5px;
}
.email-field {
margin-bottom: 20px;
}
} }
} }
......
...@@ -27,22 +27,30 @@ ...@@ -27,22 +27,30 @@
</label> </label>
<input class="field-input input-text" type="text" id="title" name="title" /> <input class="field-input input-text" type="text" id="title" name="title" />
<label class="field-label" for="title">{% trans "Email" %}
<span class="required">* {% trans "required" %}</span>
</label>
<div class="email-field">
<input class="field-input input-text" aria-describedby="email-hint" type="email" id="email" name="email" />
<span id="email-hint">{% trans "Institution email is preferred" %}</span>
</div>
<label class="field-label">{% trans "Organization" %} <label class="field-label">{% trans "Organization" %}
<span class="required">* {% trans "required" %}</span> <span class="required">* {% trans "required" %}</span>
</label> </label>
{% if edit_mode %} {% if organizations.count > 1 %}
<span class="read-only-field">{{ organization_name }}</span> <select class="field-input input-select" id="id_organization" name="organization">
{{ course_form.organization }} <option value="------">-----</option>
{% for organization in organizations %}
<option value="{{ organization.id }}">{{ organization.name }}</option>
{% endfor%}
</select>
{% else %} {% else %}
{% if course_form.organization.field.queryset.all.count > 1 %} {% with organizations|first as organization %}
{{ course_form.organization }} <span class="read-only-field">{{ organization.name }}</span>
{% else %} <input id="id_organization" name="organization" type="hidden" value="{{ organization.id }}">
{% with course_form.organization.field.queryset|first as organization %} {% endwith %}
<span class="read-only-field">{{ organization.name }}</span>
<input id="id_organization" name="organization" type="hidden" value="{{ organization.id }}">
{% endwith %}
{% endif %}
{% endif %} {% endif %}
<label class="field-label" for="bio">{% trans "Bio" %} <label class="field-label" for="bio">{% trans "Bio" %}
......
...@@ -20,6 +20,13 @@ ...@@ -20,6 +20,13 @@
</div> </div>
<div class="info-item"> <div class="info-item">
<div class="heading">{% trans "Email" %}:
{% include "publisher/course_run_detail/_clipboard.html" %}
</div>
<div class="copy email"></div>
</div>
<div class="info-item">
<div class="heading">{% trans "Organization" %}: <div class="heading">{% trans "Organization" %}:
{% include "publisher/course_run_detail/_clipboard.html" %} {% include "publisher/course_run_detail/_clipboard.html" %}
</div> </div>
......
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