diff --git a/django-verdant/rca/help_text.py b/django-verdant/rca/help_text.py index a79536a4d..46576deb2 100644 --- a/django-verdant/rca/help_text.py +++ b/django-verdant/rca/help_text.py @@ -23,7 +23,7 @@ ('rca.ProgrammePage', 'programme_specification'): 'The link to download the programme specification', ('rca.ProgrammePage', 'twitter_feed'): 'Replace the default Twitter feed by providing an alternative Twitter handle, hashtag or search term', ('rca.ProgrammePage', 'feed_image'): 'The image displayed in content feeds, such as the news carousel. Should be 16:9 ratio.', - + ('rca.NewsIndex', 'twitter_feed'): 'Replace the default Twitter feed by providing an alternative Twitter handle (without the @ symbol)', ('rca.NewsIndex', 'feed_image'): 'The image displayed in content feeds, such as the news carousel. Should be 16:9 ratio.', ('rca.NewsItem', 'listing_intro'): 'Used only on pages listing news items', @@ -65,7 +65,6 @@ ('rca.AlumniPage', 'listing_intro'): 'Used only on pages displaying a list of pages of this type', ('rca.AlumniPage', 'feed_image'): 'The image displayed in content feeds, such as the news carousel. Should be 16:9 ratio.', ('rca.StaffPage', 'school'): 'Please complete this field for academic and administrative staff only', - ('rca.StaffPage', 'staff_location'): 'Please complete this field for technical staff only', ('rca.StaffPage', 'listing_intro'): 'Used only on pages displaying a list of pages of this type', ('rca.StaffPage', 'supervised_student_other'): "Enter names of research students here who don't have a student profile. Supervised students with profile pages are pulled in automatically.", ('rca.StaffPage', 'feed_image'): 'The image displayed in content feeds, such as the news carousel. Should be 16:9 ratio.', diff --git a/django-verdant/rca/migrations/0087_staffpage_migrate_area_data_to_roles.py b/django-verdant/rca/migrations/0087_staffpage_migrate_area_data_to_roles.py new file mode 100644 index 000000000..9bcad5529 --- /dev/null +++ b/django-verdant/rca/migrations/0087_staffpage_migrate_area_data_to_roles.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-07-06 21:26 +from __future__ import unicode_literals + +from django.db import migrations +from django.db.models import Count + + +class Migration(migrations.Migration): + def move_area_data(apps, schema_editor): + """ + Move StaffPage.area to StaffPageRole.area if the user is + academic/administrative and if StaffPageRole.area is empty. + """ + StaffPage = apps.get_model('rca', 'StaffPage') + + # Move area data to roles + records = StaffPage.objects \ + .prefetch_related('roles') \ + .annotate(roles_count=Count('roles__id')) \ + .filter(staff_type__in=('administrative', 'academic')) \ + .filter(area__isnull=False) \ + .filter(roles_count__gt=0) + + for staff in records: + first_role = staff.roles.filter(area__isnull=True).first() + if first_role is not None: + first_role.area = staff.area + first_role.save() + + dependencies = [ + ('rca', '0086_schoolpageresearchlinks_allow_to_add_external_links'), + ] + + operations = [ + migrations.RunPython(move_area_data) + ] diff --git a/django-verdant/rca/migrations/0088_staffpage_move_area_and_location_field_to_roles.py b/django-verdant/rca/migrations/0088_staffpage_move_area_and_location_field_to_roles.py new file mode 100644 index 000000000..779be8cbd --- /dev/null +++ b/django-verdant/rca/migrations/0088_staffpage_move_area_and_location_field_to_roles.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2017-07-06 21:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rca', '0087_staffpage_migrate_area_data_to_roles'), + ] + + operations = [ + migrations.RemoveField( + model_name='staffpage', + name='area', + ), + migrations.RemoveField( + model_name='staffpage', + name='staff_location', + ), + migrations.AddField( + model_name='staffpagerole', + name='location', + field=models.CharField(blank=True, choices=[(b'ceramicsgsmj', b'Ceramics, Glass, Metalwork & Jewellery'), (b'darwinworshops', b'Darwin Workshops'), (b'fashiontextiles', b'Fashion & Textiles'), (b'lensbasedmediaaudio', b'Lens-based Media and Audio'), (b'paintingsculpture', b'Painting & Sculpture'), (b'printmakingletterpress', b'Printmaking & Letterpress'), (b'rapidform', b'Rapidform')], help_text=b'', max_length=255), + ), + migrations.AlterField( + model_name='staffpage', + name='staff_type', + field=models.CharField(choices=[(b'academic', b'Academic'), (b'technical', b'Technical'), (b'administrative', b'Administrative')], help_text=b'', max_length=255), + ), + migrations.AlterField( + model_name='staffpagerole', + name='title', + field=models.CharField(help_text=b'', max_length=255, verbose_name=b'Job title'), + ), + ] diff --git a/django-verdant/rca/models.py b/django-verdant/rca/models.py index 1457b579b..3fbd4272c 100644 --- a/django-verdant/rca/models.py +++ b/django-verdant/rca/models.py @@ -832,12 +832,12 @@ class ProgrammePage(Page, SocialFields, SidebarBehaviourFields): programme_specification_document = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text=help_text('rca.ProgrammePage', 'programme_specification', default="Download the programme specification")) ma_programme_description_link = models.ForeignKey(Page, null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=help_text('rca.ProgrammePage', 'ma_programme_description_link')) ma_programme_description_link_text = models.CharField(max_length=255, blank=True, help_text=help_text('rca.ProgrammePage', 'ma_programme_description_link_text')) - + ma_programme_staff_link = models.URLField("Programme staff link", blank=True, help_text=help_text('rca.ProgrammePage', 'ma_programme_staff_link')) ma_programme_staff_link_text = models.CharField(max_length=255, blank=True, help_text=help_text('rca.ProgrammePage', 'ma_programme_staff_link_text')) ma_programme_overview_link = models.ForeignKey(Page, null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=help_text('rca.ProgrammePage', 'ma_programme_overview_link')) ma_programme_overview_link_text = models.CharField(max_length=255, blank=True, help_text=help_text('rca.ProgrammePage', 'ma_entry_requirements_link_text')) - + ma_entry_requirements_link = models.ForeignKey(Page, null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=help_text('rca.ProgrammePage', 'ma_entry_requirements_link')) ma_entry_requirements_link_text = models.CharField(max_length=255, blank=True, help_text=help_text('rca.ProgrammePage', 'ma_programme_overview_link_text')) facilities_link = models.ForeignKey(Page, null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=help_text('rca.ProgrammePage', 'facilities_link')) @@ -3015,12 +3015,14 @@ class JobsIndex(Page, SocialFields): class StaffPageCarouselItem(Orderable, CarouselItemFields): page = ParentalKey('rca.StaffPage', related_name='carousel_items') + class StaffPageRole(Orderable): page = ParentalKey('rca.StaffPage', related_name='roles') - title = models.CharField(max_length=255, help_text=help_text('rca.StaffPageRole', 'title')) + title = models.CharField('Job title', max_length=255, help_text=help_text('rca.StaffPageRole', 'title')) school = models.ForeignKey('taxonomy.School', null=True, blank=True, on_delete=models.SET_NULL, related_name='staff_roles', help_text=help_text('rca.StaffPageRole', 'school')) programme = models.ForeignKey('taxonomy.Programme', null=True, blank=True, on_delete=models.SET_NULL, related_name='staff_roles', help_text=help_text('rca.StaffPageRole', 'programme')) area = models.ForeignKey('taxonomy.Area', null=True, blank=True, on_delete=models.SET_NULL, related_name='staff_roles', help_text=help_text('rca.StaffPageRole', 'area')) + location = models.CharField(max_length=255, blank=True, choices=STAFF_LOCATION_CHOICES, help_text=help_text('rca.StaffPageRole', 'location')) email = models.EmailField(max_length=255, blank=True, help_text=help_text('rca.StaffPageRole', 'email')) api_fields = [ @@ -3028,6 +3030,7 @@ class StaffPageRole(Orderable): 'school', 'programme', 'area', + 'location' 'email', ] @@ -3036,9 +3039,36 @@ class StaffPageRole(Orderable): FieldPanel('school'), FieldPanel('programme'), FieldPanel('area'), + FieldPanel('location'), FieldPanel('email'), ] + def clean(self): + if hasattr(self, 'page'): + # Will display all errors at the same time, so need to create a dict + errors = {x: [] for x in ('area', 'locatgion', 'programme', 'school')} + + # Staff location must be only for technical staff + if self.page.staff_type != 'technical' and self.location: + errors['location'].append('Location can be assigned only to technical staff') + + # School and programme must be only for academic staff + if self.page.staff_type != 'academic': + if self.school: + errors['school'].append('School can be assigned only to academic staff.') + + if self.programme: + errors['programme'].append('Programme can be only assigned to academic staff.') + + # Area cannot be filled in when staff is non-academic/administrative + if self.page.staff_type not in ('academic', 'administrative') and self.area: + errors['area'].append('Area can be only assigned to academic or administrative staff.') + + # If there are any errors in our dict, raise them. + if any(errors.itervalues()): + raise ValidationError(errors) + + class StaffPageCollaborations(Orderable): page = ParentalKey('rca.StaffPage', related_name='collaborations') title = models.CharField(max_length=255, help_text=help_text('rca.StaffPageCollaborations', 'title')) @@ -3089,10 +3119,8 @@ class StaffPagePublicationExhibition(Orderable): ] class StaffPage(Page, SocialFields): - area = models.ForeignKey('taxonomy.Area', null=True, blank=True, on_delete=models.SET_NULL, related_name='staff', help_text=help_text('rca.StaffPage', 'area')) profile_image = models.ForeignKey('rca.RcaImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text=help_text('rca.StaffPage', 'profile_image')) - staff_type = models.CharField(max_length=255, blank=True, choices=STAFF_TYPES_CHOICES, help_text=help_text('rca.StaffPage', 'staff_type')) - staff_location = models.CharField(max_length=255, blank=True, choices=STAFF_LOCATION_CHOICES, help_text=help_text('rca.StaffPage', 'staff_location')) + staff_type = models.CharField(max_length=255, choices=STAFF_TYPES_CHOICES, help_text=help_text('rca.StaffPage', 'staff_type')) twitter_feed = models.CharField(max_length=255, blank=True, help_text=help_text('rca.StaffPage', 'twitter_feed')) intro = RichTextField(help_text=help_text('rca.StaffPage', 'intro'), blank=True) biography = RichTextField(help_text=help_text('rca.StaffPage', 'biography'), blank=True) @@ -3115,8 +3143,10 @@ class StaffPage(Page, SocialFields): ad_username = models.CharField(max_length=255, blank=True, verbose_name='AD username', help_text=help_text('rca.StaffPage', 'ad_username')) search_fields = Page.search_fields + [ - index.RelatedFields('area', [ - index.SearchField('display_name'), + index.RelatedFields('roles', [ + index.RelatedFields('area', [ + index.SearchField('display_name'), + ]), ]), index.SearchField('get_staff_type_display'), index.SearchField('intro'), @@ -3124,10 +3154,8 @@ class StaffPage(Page, SocialFields): ] api_fields = [ - 'area', 'profile_image', 'staff_type', - 'staff_location', 'twitter_feed', 'intro', 'biography', @@ -3162,10 +3190,8 @@ class StaffPage(Page, SocialFields): FieldPanel('first_name'), FieldPanel('last_name'), ], 'Full name'), - FieldPanel('area'), ImageChooserPanel('profile_image'), FieldPanel('staff_type'), - FieldPanel('staff_location'), InlinePanel('roles', label="Roles"), FieldPanel('intro', classname="full"), FieldPanel('biography', classname="full"), @@ -3256,8 +3282,6 @@ def serve(self, request): # Area area_options = Area.objects.filter( id__in=StaffPageRole.objects.values_list('area', flat=True) - ) | Area.objects.filter( - id__in=StaffPage.objects.values_list('area', flat=True) ) area = area_options.filter(slug=area_slug).first() @@ -3274,10 +3298,7 @@ def serve(self, request): ) if area: - staff_pages = staff_pages.filter( - models.Q(area=area) | - models.Q(roles__area=area) - ) + staff_pages = staff_pages.filter(roles__area=area) if staff_type: staff_pages = staff_pages.filter(staff_type=staff_type) diff --git a/django-verdant/rca/static/rca/js/staff-page-editor.js b/django-verdant/rca/static/rca/js/staff-page-editor.js new file mode 100644 index 000000000..15853d9dc --- /dev/null +++ b/django-verdant/rca/static/rca/js/staff-page-editor.js @@ -0,0 +1,171 @@ +/** + * This file contains scripts executed in the admin panel on StaffPage + * editor that enable to have dependency on staff role and staff type. + */ +(function() { + /** + * Make sure staff type matches particular role fields. + */ + $(document).ready(function() { + // Make sure we are dealing with the editor of StaffPage, + // otherwise do not execute. + if (!$('body').hasClass('model-staffpage') && !$('body').hasClass('page-editor')) { + return; + } + + // Evaluate form values when it is first loaded + evaluateStaffPageRoles(); + + // Evaluate form when staff type changes. + getStaffTypeElement().on('change', evaluateStaffPageRoles); + + // Evaluate form after a new inline row is addded. + $('#id_roles-ADD').on('click', evaluateStaffPageRoles); + + // When form is submitted, empty unnecessary values. + $('#page-edit-form').submit(submitStaffPageForm); + }); + + /** + * Get staff type element. + */ + function getStaffTypeElement() { + return $('#id_staff_type'); + } + + /** + * Get staff type currently selected in the form. + */ + function getSelectedStaffType() { + return getStaffTypeElement().val(); + } + + /** + * Get forms number. + */ + function getFormsNumber() { + return $('#id_roles-TOTAL_FORMS').val() || 0; + } + + /** + * Get DOM element for given field. + * @param {Number} inlineId Row number of the inline form. + * @param {String} fieldName Field name + */ + function getFieldInputElement(inlineId, fieldName) { + return $('#id_roles-' + inlineId + '-' + fieldName); + } + + /** + * Empty fields that are not for our staff type before submitting them + * back to the server. + */ + function submitStaffPageForm() { + var staffType = getSelectedStaffType(); + + for (var i = 0; i < getFormsNumber(); i++) { + if (staffType === 'academic') { + emptyFields(['location'], i); + } else if (staffType === 'technical') { + emptyFields(['school', 'programme', 'area'], i); + } else if (staffType === 'administrative') { + emptyFields(['school', 'programme', 'location'], i); + } + } + } + + /** + * Evaluate staff page role inline forms and change visibility of the fields + * according to the staff type. + */ + function evaluateStaffPageRoles() { + for (var i = 0; i < getFormsNumber(); i++) { + evaluateStaffPageRole(i); + } + } + + /** + * Empty fields that are not for the chosen staff type. + * + * @param {Array} fieldNames Array of strings containing field names. + * @param {Number} inlineId Row number of inline form. + */ + function emptyFields(fieldNames, inlineId) { + for (var i = 0; i < fieldNames.length; i++) { + var field = getFieldInputElement(inlineId, fieldNames[i]); + + if (field) { + field.val(''); + } + } + } + + /** + * Evaluate inline form fields for staff roles for particular inline form + * and toggle their visibility accordingly to the staff type. + * @param {Number} inlineId + */ + function evaluateStaffPageRole(inlineId) { + var staffType = getSelectedStaffType(); + + if (staffType === 'technical') { + hideFields(['area', 'school', 'programme'], inlineId); + showFields(['location'], inlineId); + } else if (staffType === 'academic') { + showFields(['area', 'school', 'programme'], inlineId); + hideFields(['location'], inlineId); + } else if (staffType === 'administrative') { + showFields(['area'], inlineId); + hideFields(['location', 'school', 'programme'], inlineId); + } else { + showFields(['area', 'location', 'school', 'programme'], inlineId); + } + } + + /** + * Hide fields for given form inline. + * @param {Array} fieldNames Array of strings containing field names to hide. + * @param {Number} inlineId Inline form row number + */ + function hideFields(fieldNames, inlineId) { + toggleVisibilityOfFields(fieldNames, inlineId, 'hide'); + } + + /** + * Show fields for given form inline. + * @param {Array} fieldNames Array of strings containing fields names to show. + * @param {Number} inlineId Row number of inline form. + */ + function showFields(fieldNames, inlineId) { + toggleVisibilityOfFields(fieldNames, inlineId, 'show'); + } + + /** + * Toggle visibility of given fields for inline row. + * @param {Array} fieldNames Array of strings with field names. + * @param {Number} inlineId Row number of the form inline. + * @param {String} show_or_hide 'show' or 'hide', keep empty for toggling. + */ + function toggleVisibilityOfFields(fieldNames, inlineId, show_or_hide) { + for (var i = 0; i < fieldNames.length; i++) { + var field = getFieldInputElement(inlineId, fieldNames[i]); + + // If field exists, hide the first encountered parent
{{ self.area.display_name }}
- {% endif %} - {% if self.staff_type == "technical" %} -{{ self.get_staff_location_display }}
- {% endif %} {% with self.roles.all as roles %} {% if roles %} {% for role in roles|slice:":3" %} {% comment %}just in case they add more than 3 {% endcomment %} @@ -39,6 +31,12 @@{{ role.title }}