class DirBrowsePage(PublicBasePage): body = StreamField(DefaultBodyFields(), blank=True, null=True) dir_browse_script_url = CharField(max_length=255, blank=False) search_fields = PublicBasePage.search_fields + [ index.SearchField('body'), ] subpage_types = [] content_panels = Page.content_panels + [ StreamFieldPanel('body'), FieldPanel('dir_browse_script_url') ] + PublicBasePage.content_panels
class ExhibitChildPage(PublicBasePage): """ Pages for web exhibit child pages. """ body = StreamField(DefaultBodyFields(), blank=True) subpage_types = ['lib_collections.ExhibitChildPage'] content_panels = Page.content_panels + [ StreamFieldPanel('body'), ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.SearchField('body'), ] def get_context(self, request): context = super(ExhibitChildPage, self).get_context(request) exhibit = self.get_parent_of_type('exhibit page') footer_img = exhibit.get_web_exhibit_footer_img( self.location_and_hours['page_location'].id) font = DEFAULT_WEB_EXHIBIT_FONT if exhibit.font_family: font = exhibit.font_family context['branding_color'] = exhibit.branding_color context['font_family'] = font context['google_font_link'] = exhibit.google_font_link context['footer_img'] = footer_img context['has_exhibit_footer'] = not (not footer_img) context['is_web_exhibit'] = True context['related_collections'] = exhibit.get_related_collections( request) context['exhibit_open_date'] = exhibit.exhibit_open_date context['exhibit_close_date'] = exhibit.exhibit_close_date context['exhibit_close_date'] = exhibit.exhibit_location return context
class AskPage(PublicBasePage, ContactFields): """ Page type for Ask A Librarian pages. """ ask_widget_name = models.CharField(max_length=100, blank=True) reference_resources = RichTextField(blank=True) body = StreamField(DefaultBodyFields()) phone_regex = RegexValidator(regex=PHONE_FORMAT, message=PHONE_ERROR_MSG) secondary_phone_number = models.CharField(validators=[phone_regex], max_length=12, blank=True) schedule_appointment_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text='Link to a contact form') visit_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text='Link to a location or hours page') subpage_types = ['public.StandardPage', 'public.PublicRawHTMLPage'] content_panels = Page.content_panels + [ FieldPanel('ask_widget_name'), FieldPanel('reference_resources'), MultiFieldPanel([ PageChooserPanel('link_page'), FieldPanel('link_external'), ], heading='Contact Form'), MultiFieldPanel([ FieldPanel('email'), FieldPanel('phone_number'), FieldPanel('secondary_phone_number'), PageChooserPanel('visit_page'), PageChooserPanel('schedule_appointment_page'), ], heading='General Contact'), StreamFieldPanel('body'), ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.SearchField('ask_widget_name'), index.SearchField('reference_resources'), index.SearchField('body'), index.SearchField('email'), index.SearchField('email_label'), index.SearchField('phone_number'), index.SearchField('body'), ] @property def ask_form_name(self): """ Get the name of the chat widget. Returns: String, name of the ask widget. """ return self.ask_widget_name @property def contact_link(self): """ Return an html link for contacting a librarian by email. """ text = '<i class="fa fa-envelope-o fa-2x"></i> Email' if self.link_page: return '<a href="%s">%s</a>' % (self.link_page.url, text) elif self.email: return '<a href="mailto:%s">%s</a>' % (self.email, text) else: return False @property def has_right_sidebar(self): """ Determine if a right sidebar should be displayed on AskPages. Returns: boolean """ fields = [ self.contact_link, self.phone_number, self.secondary_phone_number, self.schedule_appointment_page, self.visit_page ] if self.base_has_right_sidebar(): return True else: for field in fields: if field: return True return False def get_context(self, request): context = super(AskPage, self).get_context(request) context['ask_pages'] = AskPage.objects.live() return context
class AskPage(PublicBasePage, ContactFields): """ Page type for Ask A Librarian pages. """ intro = StreamField( [ ('paragraph', RichTextBlock()), ('reusable_content_block', ReusableContentBlock()), ('html', RawHTMLBlock()), ], null=True, blank=True, ) ask_widget_name = models.CharField(max_length=100, blank=True) body = StreamField(DefaultBodyFields( null=True, blank=True, )) reference_resources = RichTextField( blank=True, help_text='Links to guide links and other \ Ask pages. Make new sections with Header 3' ) phone_regex = RegexValidator(regex=PHONE_FORMAT, message=PHONE_ERROR_MSG) secondary_phone_number = models.CharField( validators=[phone_regex], max_length=12, blank=True, verbose_name="SMS Number" ) schedule_appointment_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text='Shows up as Schedule icon link. Link to a contact form' ) visit_page = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL, help_text='Link to a location or hours page' ) subpage_types = ['public.StandardPage', 'public.PublicRawHTMLPage'] content_panels = Page.content_panels + [ StreamFieldPanel('intro'), FieldPanel('ask_widget_name'), StreamFieldPanel('body'), MultiFieldPanel( [ FieldPanel('phone_number'), FieldPanel('secondary_phone_number'), PageChooserPanel('visit_page'), PageChooserPanel('schedule_appointment_page'), ], heading='Other Ways to Ask: General Contact' ), MultiFieldPanel( [ PageChooserPanel('link_page'), FieldPanel('link_external'), FieldPanel('email'), ], heading='Other Ways to Ask: Email Icon Link', help_text='Shows up as Email icon link. Can only have one.' ), FieldPanel('reference_resources'), ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.SearchField('ask_widget_name'), index.SearchField('reference_resources'), index.SearchField('body'), index.SearchField('email'), index.SearchField('email_label'), index.SearchField('phone_number'), index.SearchField('body'), ] @property def ask_form_name(self): """ Get the name of the chat widget. Returns: String, name of the ask widget. """ return self.ask_widget_name @property def contact_link(self): """ Return an html link for contacting a librarian by email. """ text = '<span class="material-icons ask-icons" aria-hidden="true">mail_outline</span> Email' if self.link_page: return '<a href="%s">%s</a>' % (self.link_page.url, text) elif self.email: return '<a href="mailto:%s">%s</a>' % (self.email, text) else: return False @property def has_right_sidebar(self): """ Determine if a right sidebar should be displayed on AskPages. Returns: boolean """ fields = [ self.contact_link, self.phone_number, self.secondary_phone_number, self.schedule_appointment_page, self.visit_page ] if self.base_has_right_sidebar(): return True else: for field in fields: if field: return True return False def get_context(self, request): context = super(AskPage, self).get_context(request) context['ask_pages'] = AskPage.objects.live() return context
class StaffPage(BasePageWithoutStaffPageForeignKeys): """ Staff profile content type. """ subpage_types = ['base.IntranetPlainPage'] # editable by HR. cnetid = CharField( blank=False, help_text= 'Campus-wide unique identifier which links this record to the campus directory.', max_length=255) chicago_id = CharField(blank=True, help_text='Campus-wide unique identifier', max_length=9) display_name = CharField( blank=True, help_text='Version of this staff person\'s name to display.', max_length=255, null=True) official_name = CharField( blank=True, help_text='Official version of this staff person\'s name.', max_length=255, null=True) first_name = CharField(blank=True, help_text='First name, for sorting.', max_length=255, null=True) middle_name = CharField(blank=True, help_text='Middle name, for sorting.', max_length=255, null=True) last_name = CharField(blank=True, help_text='Last name, for sorting.', max_length=255, null=True) position_title = CharField(blank=True, help_text='Position title.', max_length=255, null=True) employee_type = IntegerField( choices=EMPLOYEE_TYPES, default=1, help_text='Clerical, exempt, non-exempt, IT or Librarian.') position_eliminated = BooleanField( default=False, help_text='Position will not be refilled.') supervisor_override = models.ForeignKey( 'staff.StaffPage', blank=True, help_text= 'If supervisor cannot be determined by the staff person\'s unit, specify supervisor here.', null=True, on_delete=models.SET_NULL, related_name='supervisor_override_for') supervises_students = BooleanField(default=False, help_text='For HR reporting.') profile_picture = models.ForeignKey( 'wagtailimages.Image', blank=True, help_text= 'Profile pictures should be frontal headshots, preferrably on a gray background.', null=True, on_delete=models.SET_NULL, related_name='+') pronouns = CharField( blank=True, help_text='Your pronouns, example: (they/them/theirs)', max_length=255, null=True) libguide_url = models.URLField( blank=True, help_text='Your profile page on guides.lib.uchicago.edu.', max_length=255, null=True) bio = StreamField( DefaultBodyFields(), blank=True, help_text='A brief bio highlighting what you offer to Library users.', null=True) cv = models.ForeignKey('wagtaildocs.Document', blank=True, help_text='Your CV or resume.', null=True, on_delete=models.SET_NULL, related_name='+') is_public_persona = BooleanField( default=False, help_text='(display changes not yet implemented)') orcid_regex = RegexValidator(regex=ORCID_FORMAT, message=ORCID_ERROR_MSG) orcid = CharField(blank=True, help_text='See https://orcid.org for more information.', max_length=255, null=True, validators=[orcid_regex]) objects = StaffPageManager() def get_employee_type(self): """ Get the serialized employee type for display in the api. Converts the integer representation from the database to a human readable string. Returns: String """ try: return EMPLOYEE_TYPES[0][self.employee_type] except (IndexError): return '' def get_serialized_units(self): """ Return a serialized list of library units assigned to the staff member. Returns: List """ return [ str(Page.objects.get(id=u['library_unit_id'])) for u in self.staff_page_units.values() ] api_fields = [ APIField('cnetid'), APIField('employee_type', serializer=serializers.CharField(source='get_employee_type')), APIField('position_title'), APIField('position_eliminated'), APIField('supervises_students'), APIField( 'library_units', serializer=serializers.ListField(source='get_serialized_units')), ] @property def get_staff_subjects(self): """ Get the subjects beloning to the staff member - UNTESTED """ # MT note: get_public_profile is an undefined value; this # should be fixed pass # return get_public_profile('elong') @property def is_subject_specialist(self): """ See if the staff member is a subject specialist - PLACEHOLDER """ return self.get_subjects() != '' @property def public_page(self): """ Get a public staff profile page for the library expert if one exists. """ from public.models import StaffPublicPage # Should try to do better try: return StaffPublicPage.objects.get(cnetid=self.cnetid) except (IndexError): return None @property def get_supervisors(self): if self.supervisor_override: return [self.supervisor_override] else: supervisors = [] for u in self.staff_page_units.all(): try: if u.library_unit.department_head.cnetid == self.cnetid: p = u.library_unit.get_parent().specific if p.department_head: supervisors.append(p.department_head) else: supervisors.append(u.library_unit.department_head) except AttributeError: continue return supervisors def get_subjects(self): """ Get all the subjects for a staff member. Must return a string for elasticsearch. Returns: String, concatenated list of subjects. """ subject_list = self.staff_subject_placements.values_list('subject', flat=True) return '\n'.join( Subject.objects.get(id=subject).name for subject in subject_list) def get_subject_objects(self): """ Get all the subject objects for a staff member. Returns: Set of subjects for a staff member. """ subject_ids = (Subject.objects.get(id=sid) for sid in self.staff_subject_placements.values_list( 'subject_id', flat=True)) return set(subject_ids) def get_staff(self): """ Get a queryset of the staff members this person supervises. TO DO: include a parameter that controls whether this is recursive or not. If it's recursive it should look into the heirarchy of UnitPages to get sub-staff. Returns: a queryset of StaffPage objects. """ cnetids = set() for s in StaffPage.objects.all(): if not s == self: if s.supervisor_override: cnetids.add(s.cnetid) else: for u in s.staff_page_units.filter( library_unit__department_head=self): try: cnetids.add(u.page.cnetid) except: continue return StaffPage.objects.filter(cnetid__in=list(cnetids)) @staticmethod def get_staff_by_building(building_str): building = 0 for b in BUILDINGS: if b[1] == building_str: building = b[0] if building > 0: return StaffPage.objects.live().filter( staff_page_units__library_unit__building=building).distinct() else: return StaffPage.objects.none() content_panels = Page.content_panels + [ ImageChooserPanel('profile_picture'), FieldPanel('pronouns'), StreamFieldPanel('bio'), DocumentChooserPanel('cv'), FieldPanel('libguide_url'), FieldPanel('is_public_persona'), InlinePanel('staff_subject_placements', label='Subject Specialties'), InlinePanel('expertise_placements', label='Expertise'), FieldPanel('orcid') ] + BasePageWithoutStaffPageForeignKeys.content_panels # for a thread about upcoming support for read-only fields, # see: https://github.com/wagtail/wagtail/issues/2893 human_resources_panels = [ FieldPanel('cnetid'), MultiFieldPanel( [ FieldPanel('display_name'), FieldPanel('official_name'), FieldPanel('first_name'), FieldPanel('middle_name'), FieldPanel('last_name'), FieldPanel('position_title'), InlinePanel('staff_page_email', label='Email Addresses'), InlinePanel('staff_page_phone_faculty_exchange', label='Phone Number and Faculty Exchange'), InlinePanel('staff_page_units', label='Library Units'), FieldPanel('employee_type'), FieldPanel('position_eliminated'), FieldPanel('supervises_students'), PageChooserPanel('supervisor_override'), ], heading= 'Human-resources editable fields. These fields will push to the campus directory (where appropriate).' ), MultiFieldPanel( [ FieldPanel('chicago_id'), ], heading= 'Read-only fields. These values are pulled from the campus directory.' ) ] search_fields = BasePageWithoutStaffPageForeignKeys.search_fields + [ index.SearchField('profile_picture'), index.SearchField('cv'), index.SearchField('libguide_url'), index.SearchField('orcid'), index.SearchField('position_title') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ObjectList(human_resources_panels, heading='Human Resources Info'), ]) class Meta: ordering = ['last_name', 'first_name'] def get_context(self, request): position_title = self.position_title emails = self.staff_page_email.all().values_list('email', flat=True) units = set() for staff_page_unit in self.staff_page_units.all(): try: unit_title = staff_page_unit.library_unit.get_full_name() except: unit_title = None try: unit_url = staff_page_unit.library_unit.intranet_unit_page.first( ).url except: unit_url = None units.add(json.dumps({'title': unit_title, 'url': unit_url})) units = list(map(json.loads, list(units))) subjects = [] for subject in self.staff_subject_placements.all(): subjects.append({'name': subject.subject.name, 'url': ''}) group_memberships = [] for group_membership in self.member.all(): if group_membership.parent.is_active: group_memberships.append({ 'group': { 'title': group_membership.parent.title, 'url': group_membership.parent.url }, 'role': group_membership.role }) context = super(StaffPage, self).get_context(request) context['position_title'] = position_title context['emails'] = emails context['units'] = units context['subjects'] = subjects context['group_memberships'] = group_memberships return context
class ConferencePage(PublicBasePage, SocialMediaFields): """ Main page for creating conferences. """ # Generic variables hex_regex = RegexValidator(regex='^#[a-zA-Z0-9]{6}$', \ message='Please enter a hex color, e.g. #012043') # Field definitions primary_branding_color= models.CharField(validators=[hex_regex], \ max_length=7, blank=True) secondary_branding_color= models.CharField(validators=[hex_regex], \ max_length=7, blank=True) location = models.ForeignKey( 'public.LocationPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='%(app_label)s_%(class)s_related') start_date = models.DateTimeField(blank=True, null=True) end_date = models.DateTimeField(blank=True, null=True) current = models.BooleanField(default=True, \ help_text="Uncheck this when the conference has transpired") conference_logo = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) secondary_registration_heading = models.CharField(max_length=40, blank=True) secondary_registration_description = models.TextField(blank=True) body = StreamField(DefaultBodyFields()) # Panels and subpage types content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('banner_image'), FieldPanel('banner_title'), FieldPanel('banner_subtitle'), ], heading='Banner'), MultiFieldPanel( [ FieldPanel('primary_branding_color'), FieldPanel('secondary_branding_color'), ImageChooserPanel('conference_logo'), ], heading='Branding', ), MultiFieldPanel( [ FieldPanel('start_date'), FieldPanel('end_date'), ], heading='Dates and Times', ), FieldPanel('location'), FieldPanel('current'), InlinePanel('main_registration', label='Main Registration Link'), MultiFieldPanel( [ FieldPanel('secondary_registration_heading'), FieldPanel('secondary_registration_description'), InlinePanel('sub_registration', label='Secondary Registration Link'), ], heading='Secondary Registration Links', ), InlinePanel('sponsors', label='Sponsors'), InlinePanel('organizers', label='Organizers'), StreamFieldPanel('body'), ] + SocialMediaFields.panels + PublicBasePage.content_panels subpage_types = ['conferences.ConferenceSubPage', 'redirects.RedirectPage'] search_fields = PublicBasePage.search_fields + [ index.SearchField('banner_image'), index.SearchField('primary_branding_color'), index.SearchField('secondary_branding_color'), index.SearchField('location'), index.SearchField('current'), index.SearchField('conference_logo'), index.SearchField('body'), ] api_fields = ('body', ) @property def has_right_sidebar(self): """ Test to see if a right sidebar should be displayed. Returns: Boolean """ fields = [self.sponsors, self.organizers, self.sub_registration, \ self.secondary_registration_heading, self.secondary_registration_description] return self.base_has_right_sidebar() or self.has_field(fields) def has_conf_banner(self, current_site): """ Used to override the boolean [0] value for PublicBasePage get_banner. Args: current_site: object """ return self.get_banner(current_site)[0] or (self.primary_branding_color and self.banner_title) def get_banner(self, current_site): """ Override the default get_banner method so that banners will always display as long as a title is present. Args: current_site: site object. Returns: See get_banner in PublicBasePage. """ try: # Base case if self.banner_title: return (True, self.banner_image, self.banner_feature, self.banner_title, self.banner_subtitle, self.relative_url(current_site), self.title) # Recursive case else: return self.get_parent().specific.get_banner(current_site) # Reached the top of the tree (could factor this into an if) except (AttributeError): return (False, None, None, '', '', '', '') # Context def get_context(self, request): context = super(ConferencePage, self).get_context(request) current_site = Site.find_for_request(request) main_reg = self.main_registration.all() has_sidebar = self.has_left_sidebar(context) or bool(main_reg) context['has_left_sidebar'] = has_sidebar context['content_div_css'] = self.get_conditional_css_classes( 'content', has_sidebar) context['breadcrumb_div_css'] = self.get_conditional_css_classes( 'breadcrumbs', has_sidebar) context['has_banner'] = self.has_conf_banner(current_site) context['primary_branding_color'] = self.primary_branding_color context['secondary_branding_color'] = self.secondary_branding_color context['conference_logo'] = self.conference_logo context['conference_title'] = self.title context['has_social_media'] = self.has_social_media context['main_registration'] = main_reg context['sponsors'] = self.sponsors.all() context['organizers'] = self.organizers.all() context['secondary_registration'] = self.sub_registration.all() context[ 'secondary_registration_heading'] = self.secondary_registration_heading context[ 'secondary_registration_description'] = self.secondary_registration_description context['home'] = self.relative_url(current_site) return context
class ConferenceSubPage(PublicBasePage): """ Subpages for conferences. These inherit most of their template "goodness" from parent ConferencePage. """ body = StreamField(DefaultBodyFields()) content_panels = Page.content_panels + [ StreamFieldPanel('body'), ] + PublicBasePage.content_panels subpage_types = ['conferences.ConferenceSubPage'] search_fields = PublicBasePage.search_fields + [ index.SearchField('body'), ] api_fields = ('body', ) @property def has_right_sidebar(self): """ Override default test to see if a right sidebar should be displayed. Returns: Boolean """ parent = self.get_parent_of_type('conference page') return parent.has_right_sidebar @property def has_social_media(self): """ Override default test for social media. Returns: Boolean """ parent = self.get_parent_of_type('conference page') return parent.has_social_media # Context def get_context(self, request): context = super(ConferenceSubPage, self).get_context(request) current_site = Site.find_for_request(request) parent = self.get_parent_of_type('conference page') main_reg = parent.main_registration.all() has_sidebar = parent.has_left_sidebar(context) or bool(main_reg) # Set social media fields dynamically and # get all the values from the parent page. # This doesn't seem like a good practice # How else can this be done? social_media_fields = [ f.name for f in SocialMediaFields._meta.get_fields() ] for field in social_media_fields: exec('self.' + field + ' = ' + 'parent.' + field) context['primary_branding_color'] = parent.primary_branding_color context['secondary_branding_color'] = parent.secondary_branding_color context['conference_logo'] = parent.conference_logo context['conference_title'] = parent.title context['has_social_media'] = parent.has_social_media context['main_registration'] = parent.main_registration.all() context['sponsors'] = parent.sponsors.all() context['organizers'] = parent.organizers.all() context['secondary_registration'] = parent.sub_registration.all() context[ 'secondary_registration_heading'] = parent.secondary_registration_heading context[ 'secondary_registration_description'] = parent.secondary_registration_description context['home'] = parent.relative_url(current_site) return context
class LibNewsPage(PublicBasePage): def __init__(self, *args, **kwargs): super(LibNewsPage, self).__init__(*args, **kwargs) self.news_pages = LibNewsPage.objects.live( ).prefetch_related('lib_news_categories') body = StreamField(DefaultBodyFields()) thumbnail = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) alt_text = models.CharField(max_length=100, blank=True) is_feature_story = models.BooleanField(default=False) excerpt = RichTextField(blank=True) related_exhibits = StreamField( RelatedExhibitBlock(required=False), blank=True, default=[] ) by_staff_or_unit = models.ForeignKey( 'lib_news.PublicNewsAuthors', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) custom_author_byline = models.CharField(max_length=360, blank=True) published_at = models.DateTimeField(default=timezone.now) library_kiosk = models.BooleanField(default=False) law_kiosk = models.BooleanField(default=False) sciences_kiosk = models.BooleanField(default=False) scrc_kiosk = models.BooleanField(default=False) treat_as_webpage = models.BooleanField( default=False, help_text='Functionally converts this page to a standard page. \ If checked, the page will not appear in news feeds' ) exhibit_story_hours_override = RichTextField(blank=True) def get_categories(self): """ Get a list of categories assigned to the news page. Categories are cached in redis as a dictionary where the keys are page IDs and the values are lists of string category names. Returns: list of strings """ try: dcache = caches['default'] cdict = {} pid = self.id if 'news_cats' not in dcache: dcache.set('news_cats', cdict, NEWS_CACHE_TTL) if 'news_cats' in dcache: cdict = dcache.get('news_cats') if pid in cdict: cats = cdict[pid] else: cats = [ str(cat) for cat in self.lib_news_categories.get_object_list() ] cdict[pid] = cats dcache.set('news_cats', cdict, NEWS_CACHE_TTL) return cats # This is a FakeQuerySet and we are probably in preview mode. # To handle this, we won't show any categories. except (AttributeError): return [ 'Can\'t load categories in PREVIEW', 'Check categories on the LIVE page' ] @property def short_description(self): if self.excerpt: retval = self.excerpt else: html = str(self.body).replace('</p>', '</p> ') retval = text.Truncator(html).words(100, html=True) # return retval return bleach.clean( retval, tags=['a', 'b', 'i'], attributes={'a': ['href', 'rel', 'data', 'id', 'linktype']}, strip=True, strip_comments=True, ) @property def has_right_sidebar(self): """ Determine if a right sidebar should be displayed in the template. Returns: boolean """ return True def get_recent_stories(self, n, field): """ Gets the n most rescent stories sorted by the field name passed. Args: n: int, number of stories to return field: string, field to be passed to a Django QuerySet filter, e.g. '-published_at'. """ return self.news_pages.order_by(field).exclude(thumbnail=None ).exclude(id=self.id)[:n] subpage_types = [] ROW_CLASS = 'col4' content_panels = Page.content_panels + [ MultiFieldPanel( [ ImageChooserPanel('thumbnail'), FieldPanel('alt_text'), ], heading='Thumbnail', ), FieldPanel('is_feature_story'), FieldPanel('excerpt'), StreamFieldPanel('body'), InlinePanel('lib_news_categories', label='Categories'), FieldPanel('published_at'), MultiFieldPanel( [ SnippetChooserPanel('by_staff_or_unit'), FieldPanel('custom_author_byline'), ], heading='Author' ), MultiFieldPanel( [ FieldRowPanel( [ FieldPanel('law_kiosk', classname=ROW_CLASS), FieldPanel('sciences_kiosk', classname=ROW_CLASS), FieldPanel('scrc_kiosk', classname=ROW_CLASS), FieldPanel('library_kiosk', classname=ROW_CLASS), ] ) ], heading='Publish to' ), ] + PublicBasePage.content_panels widget_content_panels = [ StreamFieldPanel('related_exhibits'), FieldPanel('treat_as_webpage'), MultiFieldPanel( [ FieldPanel('quicklinks_title'), FieldPanel('quicklinks'), FieldPanel('view_more_link_label'), FieldPanel('view_more_link'), FieldPanel('change_to_callout'), ], heading='Rich Text' ), FieldPanel('exhibit_story_hours_override'), ] edit_handler = TabbedInterface( [ ObjectList(content_panels, heading='Content'), ObjectList(PublicBasePage.promote_panels, heading='Promote'), ObjectList( Page.settings_panels, heading='Settings', classname="settings" ), ObjectList(widget_content_panels, heading='Widgets'), ] ) search_fields = PublicBasePage.search_fields + [ index.SearchField('body', partial_match=True), index.SearchField('alt_text'), index.SearchField('excerpt'), index.SearchField('related_exhibits'), index.SearchField('custom_author_byline'), index.SearchField('published_at'), ] api_fields = [ APIField('is_feature_story'), APIField( 'categories', serializer=serializers.ListField(source='get_categories') ), APIField( 'thumbnail', serializer=ImageRenditionField('fill-500x425-c50') ), APIField( 'thumbnail_alt_text', serializer=serializers.CharField(source='alt_text') ), APIField('published_at'), APIField('treat_as_webpage'), ] def get_context(self, request): """ Override the page object's get context method. """ context = super(LibNewsPage, self).get_context(request) parent = self.get_parent_of_type('lib news index page') parent_context = parent.get_context(request) self.events_feed_url = parent.events_feed_url context['categories'] = parent.get_alpha_cats() context['tagged'] = self.get_categories() context['category_url_base'] = parent_context['category_url_base'] context['contacts'] = parent_context['contacts'] context['display_current_web_exhibits'] = parent_context[ 'display_current_web_exhibits'] context['current_exhibits'] = parent_context['current_exhibits'] context['events_feed'] = parent_context['events_feed'] context['recent_stories'] = self.get_recent_stories(3, '-published_at') context['content_div_css'] = parent_context['content_div_css'] context['right_sidebar_classes'] = parent_context[ 'right_sidebar_classes'] context['nav'] = parent_context['nav'] context['libra'] = parent_context['libra'] return context
class IntranetUnitsPage(BasePage, Email, PhoneNumber): """ Content type for department pages on the intranet. """ unit = models.ForeignKey('directory_unit.DirectoryUnit', related_name='intranet_unit_page', null=True, blank=True, on_delete=models.SET_NULL) intro = StreamField(DefaultBodyFields(), blank=True) internal_location = models.CharField(max_length=255, blank=True) internal_phone_number = models.CharField(max_length=255, blank=True) internal_email = models.EmailField(max_length=255, blank=True) staff_only_email = models.EmailField(max_length=254, blank=True) body = StreamField(DefaultBodyFields(), null=True, blank=True) show_staff = models.BooleanField(default=False) show_departments = models.BooleanField(default=False) subpage_types = ['base.IntranetIndexPage', 'base.IntranetPlainPage', 'intranetforms.IntranetFormPage', \ 'intranettocs.TOCPage', 'intranetunits.IntranetUnitsPage', 'intranetunits.IntranetUnitsReportsIndexPage'] search_fields = BasePage.search_fields + [ index.SearchField('intro'), index.SearchField('internal_location'), index.SearchField('internal_phone_number'), index.SearchField('internal_email'), index.SearchField('staff_only_email'), index.SearchField('body'), ] def get_context(self, request): context = super(IntranetUnitsPage, self).get_context(request) context['phone'] = '' if self.specific.internal_phone_number: context['phone'] = self.specific.internal_phone_number context['location'] = '' if self.specific.internal_location: context['location'] = self.specific.internal_location context['email'] = '' if self.specific.internal_email: context['email'] = self.specific.internal_email context['show_staff'] = self.show_staff context['show_departments'] = self.show_departments department_members = [] if self.specific.unit: units = self.specific.unit.get_descendants(True) staff_pages = [] for v in StaffPagePageVCards.objects.filter(page__live=True, unit__in=units): staff_page = v.staffpagepagevcards.page if staff_page not in staff_pages: staff_pages.append(staff_page) # sorting: supervisors first, alphabetically; then non-supervisors, alphabetically. supervisors = list( map(lambda u: u.supervisor, UnitSupervisor.objects.filter(unit=self.specific.unit))) supervisor_staff = sorted(list( set(staff_pages).intersection(supervisors)), key=lambda s: s.title) non_supervisor_staff = sorted(list( set(staff_pages).difference(supervisors)), key=lambda s: s.title) staff_pages = supervisor_staff + non_supervisor_staff for staff_page in staff_pages: titles = [] emails = [] phone_numbers = [] for v in StaffPagePageVCards.objects.filter(page=staff_page, unit__in=units): if not v.title in titles: titles.append(v.title) if not v.email in emails: emails.append(v.email) if not v.phone_number in phone_numbers: phone_numbers.append(v.phone_number) if len(emails) > 0: email = emails[0] else: email = '' department_members.append({ 'title': staff_page.title, 'url': staff_page.url, 'jobtitle': "<br/>".join(titles), 'email': email, 'phone': "<br/>".join(phone_numbers), }) context['department_members'] = department_members department_units = [] if self.unit: for directory_unit in DirectoryUnit.objects.filter( parentUnit=self.unit): intranet_unit_pages = directory_unit.intranet_unit_page.all( ).filter(live=True, show_in_menus=True) if intranet_unit_pages: unit = { 'title': intranet_unit_pages[0].title, 'url': intranet_unit_pages[0].url, 'location': intranet_unit_pages[0].internal_location, 'phone_number': intranet_unit_pages[0].internal_phone_number, 'email': intranet_unit_pages[0].internal_email } supervisors = [] for s in UnitSupervisor.objects.filter( unit=directory_unit): if s.supervisor != None: supervisors.append({ 'title': s.supervisor.title, 'url': s.supervisor.url, 'phone_number': s.supervisor.vcards.all()[0].phone_number, 'email': s.supervisor.vcards.all()[0].email, }) unit['supervisors'] = supervisors department_units.append(unit) # split the department units into lists of lists, each inner list containing 4 or less items. context['department_unit_rows'] = [ department_units[i:i + 4] for i in range(0, len(department_units), 4) ] #reports tmp = [] unit_reports_pages = IntranetUnitsReportsPage.objects.descendant_of( self) for unit_reports_page in unit_reports_pages: for r in unit_reports_page.intranet_units_reports.all(): if not r.link and not r.document.url: continue report = { 'summary': r.summary, 'date': r.date.strftime("%b. %-d, %Y"), 'sortdate': r.date.strftime("%Y%m%d") } if r.link: report['url'] = r.link elif r.document.url: report['url'] = r.document.url tmp.append(report) reports = sorted(tmp, key=lambda r: r['sortdate'], reverse=True)[:3] context['reports'] = reports return context
class NewsPage(BasePage): """ News story content type used on intranet pages. """ excerpt = RichTextField( blank=True, null=True, help_text= 'Shown on the News feed. Populated automatically from “Body” if left empty.' ) author = models.ForeignKey('staff.StaffPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='news_stories') story_date = models.DateField( default=timezone.now, help_text= 'If you use Settings to publish a future post, put the publish date here. Otherwise, leave today as the story date.' ) sticky_until = models.DateField( blank=True, null=True, help_text='To be used by Admin and HR only.') thumbnail = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') alt_text = models.CharField(max_length=100, blank=True) body = StreamField(DefaultBodyFields(), blank=False, null=False) subpage_types = [] content_panels = Page.content_panels + [ StreamFieldPanel('body'), FieldPanel('author'), FieldPanel('story_date'), MultiFieldPanel( [ ImageChooserPanel('thumbnail'), FieldPanel('alt_text'), ], heading='Thumbnail', ), FieldPanel('excerpt'), ] + BasePage.content_panels promote_panels = BasePage.promote_panels + [ FieldPanel('sticky_until'), ] search_fields = PublicBasePage.search_fields + [ index.SearchField('excerpt'), index.SearchField('author'), index.SearchField('thumbnail'), index.SearchField('body'), ] @classmethod def get_stories(cls, sticky=False, now=None): """ A handy function to efficiently get a list of news stories to display on Loop. Parameters: sticky -- A boolean. If this is set to True, the method will return sticky stories only. Setting this to False returns non-sticky stories only. now -- A datetime.date(), or None. If None, the method will set now to the current date. This parameter is present to make this function easier to test. Returns: A django.core.paginator.Paginator object for Loop news stories. """ if now == None: now = datetime.date(datetime.now()) stories = cls.objects.filter( live=True, story_date__lte=now, ).order_by('-story_date', '-latest_revision_created_at') if sticky: stories = stories.filter(sticky_until__gte=now) else: stories = stories.exclude(sticky_until__gte=now) return Paginator(stories, 10)
class ExhibitPage(PublicBasePage): """ Pages for individual exhibits. """ acknowledgments = models.TextField(null=False, blank=True, default='') short_abstract = models.TextField(null=False, blank=False, default='') full_description = StreamField(DefaultBodyFields(), blank=True, null=True) thumbnail = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') thumbnail_caption = models.TextField(null=False, blank=True, default='') staff_contact = models.ForeignKey('staff.StaffPage', null=True, blank=True, on_delete=models.SET_NULL) unit_contact = models.BooleanField(default=False) student_exhibit = models.BooleanField(default=False) exhibit_open_date = models.DateField(blank=True, null=True) exhibit_close_date = models.DateField(blank=True, null=True) exhibit_location = models.ForeignKey('public.LocationPage', null=True, blank=True, on_delete=models.SET_NULL) exhibit_daily_hours = models.CharField(blank=True, null=False, default='', max_length=255) exhibit_cost = models.CharField(blank=True, null=False, default='', max_length=255) space_type = models.CharField(null=False, blank=True, choices=(('Case', 'Case'), ('Gallery', 'Gallery')), max_length=255) web_exhibit_url = models.URLField("Web Exhibit URL", blank=True) publication_description = models.CharField(null=False, blank=True, default='', max_length=255) publication_price = models.CharField(null=False, blank=True, default='', max_length=255) publication_url = models.URLField("Publication URL", blank=True) ordering_information = models.BooleanField(default=False) exhibit_text_link_external = models.URLField("Exhibit text external link", blank=True) exhibit_text_link_page = models.ForeignKey('wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) exhibit_text_document = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) exhibit_checklist_link_external = models.URLField( "Exhibit checklist external link", blank=True) exhibit_checklist_link_page = models.ForeignKey('wagtailcore.Page', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) exhibit_checklist_document = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) # Web exhibit fields web_exhibit = models.BooleanField(default=False, help_text='Display as web exhibit') hex_regex = RegexValidator(regex='^#[a-zA-Z0-9]{6}$', \ message='Please enter a hex color, e.g. #012043') branding_color = models.CharField(validators=[hex_regex], max_length=7, blank=True) google_font_link = models.URLField( blank=True, help_text='Google fonts link to embedd in the header') font_family = models.CharField( max_length=100, blank=True, help_text='CSS font-family value, e.g. \'Roboto\', sans-serif') subpage_types = ['lib_collections.ExhibitChildPage'] web_exhibit_panels = [ FieldPanel('web_exhibit'), MultiFieldPanel([ ImageChooserPanel('banner_image'), ImageChooserPanel('banner_feature'), FieldPanel('banner_title'), FieldPanel('banner_subtitle'), ], heading='Banner'), MultiFieldPanel([ FieldPanel('branding_color'), FieldPanel('google_font_link'), FieldPanel('font_family'), ], heading='Branding'), ] content_panels = Page.content_panels + [ FieldPanel('acknowledgments'), FieldPanel('short_abstract'), StreamFieldPanel('full_description'), MultiFieldPanel( [ImageChooserPanel('thumbnail'), FieldPanel('thumbnail_caption')], heading='Thumbnail'), InlinePanel('exhibit_subject_placements', label='Subjects'), InlinePanel('exhibit_page_related_collection_placement', label='Related Collection'), InlinePanel('exhibit_page_donor_page_list_placement', label='Donor'), FieldPanel('student_exhibit'), MultiFieldPanel([ FieldPanel('exhibit_open_date'), FieldPanel('exhibit_close_date'), ], heading='Dates'), MultiFieldPanel([ FieldPanel('exhibit_location'), FieldPanel('exhibit_daily_hours'), FieldPanel('exhibit_cost'), FieldPanel('space_type'), ], heading='Visiting information'), MultiFieldPanel([ FieldPanel('web_exhibit_url'), FieldPanel('publication_description'), FieldPanel('publication_price'), FieldPanel('publication_url'), FieldPanel('ordering_information'), ], heading='Publication information'), MultiFieldPanel([ FieldPanel('exhibit_text_link_external'), PageChooserPanel('exhibit_text_link_page'), DocumentChooserPanel('exhibit_text_document') ], heading='Exhibit Text (Choose One or None)'), MultiFieldPanel([ FieldPanel('exhibit_checklist_link_external'), PageChooserPanel('exhibit_checklist_link_page'), DocumentChooserPanel('exhibit_checklist_document') ], heading='Exhibit Checklist (Choose One or None)'), MultiFieldPanel( [FieldPanel('staff_contact'), FieldPanel('unit_contact')], heading='Staff or Unit Contact') ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.FilterField('exhibit_open_date'), index.FilterField('exhibit_close_date'), index.FilterField('title'), index.FilterField('web_exhibit_url'), index.SearchField('short_abstract'), index.SearchField('full_description'), index.SearchField('thumbnail'), index.SearchField('thumbnail_caption'), index.SearchField('exhibit_location'), index.SearchField('exhibit_daily_hours'), index.SearchField('exhibit_cost'), index.SearchField('space_type'), index.SearchField('web_exhibit_url'), index.SearchField('publication_description'), index.SearchField('publication_price'), index.SearchField('publication_url'), index.SearchField('staff_contact'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(PublicBasePage.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ObjectList(web_exhibit_panels, heading='Web Exhibit'), ]) def is_web_exhibit(self): """ Determine if an ExhibitPage is a web exhibit. """ return self.web_exhibit def has_right_sidebar(self): """ Always has a right sidebar? """ return True def get_web_exhibit_footer_img(self, building): """ Get the web exhibit footer image for a specific building. Returns: Image object or None """ #building = self.location_and_hours['page_location'].id img = { SCRC_BUILDING_ID: SCRC_EXHIBIT_FOOTER_IMG, CRERAR_BUILDING_ID: CRERAR_EXHIBIT_FOOTER_IMG } if building in img: return Image.objects.get(id=img[building]) return None def get_related_collections(self, request): """ Get the related collections for a web exhibit. Args: request: object Returns: A list of tuples where the first item in the tuple is a collection title and the second item is a url. If no related collections are found, returns None. """ current_site = Site.find_for_request(request) collections = self.exhibit_page_related_collection_placement.all() related_collections = '<ul>' if collections: for collection in collections: if collection.related_collection: related_collections += '<li><a href="' + collection.related_collection.relative_url( current_site ) + '">' + collection.related_collection.title + '</a></li>' return related_collections + '</ul>' return None def get_context(self, request): staff_url = '' try: staff_url = StaffPublicPage.objects.get( cnetid=self.staff_contact.cnetid).url except: pass default_image = None default_image = Image.objects.get(title="Default Placeholder Photo") font = DEFAULT_WEB_EXHIBIT_FONT if self.font_family: font = self.font_family context = super(ExhibitPage, self).get_context(request) footer_img = self.get_web_exhibit_footer_img( self.location_and_hours['page_location'].id ) # must be set after context context['default_image'] = default_image context['staff_url'] = staff_url context['branding_color'] = self.branding_color context['font_family'] = font context['google_font_link'] = self.google_font_link context['footer_img'] = footer_img context['has_exhibit_footer'] = not (not footer_img) context['is_web_exhibit'] = self.is_web_exhibit() context['related_collections'] = self.get_related_collections(request) context['exhibit_open_date'] = self.exhibit_open_date context['exhibit_close_date'] = self.exhibit_close_date context['exhibit_close_date'] = self.exhibit_location return context
class CollectionPage(PublicBasePage): """ Pages for individual collections. """ acknowledgments = models.TextField(null=False, blank=True, default='') short_abstract = models.TextField(null=False, blank=False, default='') full_description = StreamField(DefaultBodyFields(), blank=True, null=True) access_instructions = models.TextField(null=False, blank=True, default='') thumbnail = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') thumbnail_caption = models.TextField(null=False, blank=True) primary_online_access_link_label = models.CharField(max_length=255, blank=True) primary_online_access_link_url = models.URLField( "Primary online access link URL", blank=True) collection_location = models.ForeignKey('public.LocationPage', null=True, blank=True, on_delete=models.SET_NULL) staff_contact = models.ForeignKey('staff.StaffPage', null=True, blank=True, on_delete=models.SET_NULL) unit_contact = models.BooleanField(default=False) subpage_types = ['public.StandardPage'] content_panels = Page.content_panels + [ FieldPanel('acknowledgments'), InlinePanel('alternate_name', label='Alternate Names'), FieldPanel('short_abstract'), StreamFieldPanel('full_description'), MultiFieldPanel([ ImageChooserPanel('thumbnail'), FieldPanel('thumbnail_caption'), ], heading='Thumbnail'), InlinePanel('collection_subject_placements', label='Subjects'), InlinePanel('collection_placements', label='Formats'), FieldPanel('access_instructions'), MultiFieldPanel([ FieldPanel('primary_online_access_link_label'), FieldPanel('primary_online_access_link_url'), ], heading='Primary Online Access Link'), InlinePanel('supplementary_access_links', label='Supplementary Access Links'), InlinePanel('related_collection_placement', label='Related Collection'), FieldPanel('collection_location'), InlinePanel('donor_page_list_placement', label='Donor'), MultiFieldPanel( [FieldPanel('staff_contact'), FieldPanel('unit_contact')], heading='Staff or Unit Contact') ] + PublicBasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.FilterField('title'), index.SearchField('short_abstract'), index.SearchField('full_description'), index.SearchField('thumbnail'), index.SearchField('thumbnail_caption'), index.SearchField('access_instructions'), index.SearchField('collection_location'), index.SearchField('staff_contact'), ] def get_context(self, request): staff_title = '' staff_vcard_title = '' staff_vcard_email = '' staff_vcard_phone_number = '' staff_vcard_faculty_exchange = '' try: staff_title = self.staff_contact.title staff_vcard_title = self.staff_contact.vcards.first().title staff_vcard_email = self.staff_contact.vcards.first().email staff_vcard_phone_number = self.staff_contact.vcards.first( ).phone_number staff_vcard_faculty_exchange = self.staff_contact.vcards.first( ).faculty_exchange except: pass staff_url = '' try: staff_url = StaffPublicPage.objects.get( cnetid=self.staff_contact.cnetid).url except: pass unit_title = '' unit_url = '' unit_email_label = '' unit_email = '' unit_phone_label = '' unit_phone_number = '' unit_fax_number = '' unit_link_text = '' unit_link_external = '' unit_link_page = '' unit_link_document = '' if self.unit_contact: try: unit_title = self.unit.title except: pass try: unit_url = self.unit.public_web_page.url except: pass try: unit_email_label = self.unit.email_label except: pass try: unit_email = self.unit.email except: pass try: unit_phone_label = self.unit.phone_label except: pass try: unit_phone_number = self.unit.phone_number except: pass try: unit_fax_number = self.unit.fax_number except: pass try: unit_link_text = self.unit.link_text except: pass try: unit_link_external = self.unit.link_external except: pass try: unit_link_page = self.unit.link_page.url except: pass try: unit_link_document = self.unit.link_document.file.url except: pass default_image = None default_image = Image.objects.get(title="Default Placeholder Photo") context = super(CollectionPage, self).get_context(request) context['default_image'] = default_image context['staff_title'] = staff_title context['staff_url'] = staff_url context['staff_vcard_title'] = staff_vcard_title context['staff_vcard_email'] = staff_vcard_email context['staff_vcard_phone_number'] = staff_vcard_phone_number context['staff_vcard_faculty_exchange'] = staff_vcard_faculty_exchange context['unit_title'] = unit_title context['unit_url'] = unit_url context['unit_email_label'] = unit_email_label context['unit_email'] = unit_email context['unit_phone_label'] = unit_phone_label context['unit_phone_number'] = unit_phone_number context['unit_fax_number'] = unit_fax_number context['unit_link_text'] = unit_link_text context['unit_link_external'] = unit_link_external context['unit_link_page'] = unit_link_page context['unit_link_document'] = unit_link_document context[ 'supplementary_access_links'] = self.supplementary_access_links.get_object_list( ) return context def has_right_sidebar(self): return True
class IntranetUnitsPage(BasePage, Email, PhoneNumber): """ Content type for department pages on the intranet. """ unit_page = models.ForeignKey('units.UnitPage', related_name='loop_page', null=True, blank=True, on_delete=models.SET_NULL) intro = StreamField(DefaultBodyFields(), blank=True) internal_location = models.CharField(max_length=255, blank=True) internal_phone_number = models.CharField(max_length=255, blank=True) internal_email = models.EmailField(max_length=255, blank=True) staff_only_email = models.EmailField(max_length=254, blank=True) body = StreamField(DefaultBodyFields(), null=True, blank=True) show_staff = models.BooleanField(default=False) show_departments = models.BooleanField(default=False) subpage_types = [ 'base.IntranetIndexPage', 'base.IntranetPlainPage', 'intranetforms.IntranetFormPage', 'intranettocs.TOCPage', 'intranetunits.IntranetUnitsPage', 'intranetunits.IntranetUnitsReportsIndexPage' ] search_fields = BasePage.search_fields + [ index.SearchField('intro'), index.SearchField('internal_location'), index.SearchField('internal_phone_number'), index.SearchField('internal_email'), index.SearchField('staff_only_email'), index.SearchField('body'), ] def _get_full_name_list(self): """ Helper function for get_full_name() and get_campus_directory_full_name(). This returns a list of page titles that can be processed by those two functions. """ return list( self.get_ancestors(True).live().type( IntranetUnitsPage).values_list('title', flat=True)) def get_full_name(self): """ Get an IntranetUnitsPage's full name according to Wagtail. The full name of an IntranetUnitsPage includes a breadcrumb trail of the titles its ancestor IntranetUnitsPages. Example: Wagtail contains an IntranetUnitsPage for "Collections & Access". That page contains "Access Services". The full name for Access Services is "Collections & Access - Access Services". Compare this method's output with get_campus_directory_full_name(). """ return ' - '.join(self._get_full_name_list()) def get_campus_directory_full_name(self): """ Get an IntranetUnitsPage's campus directory name. The campus directory describes a university department in a three level heirarchy: division, department, and sub-department. For library departments division is always "Library". The library's own view of its org chart has more levels than what we can represent in the campus directory, so we skip some levels to make room for the departments below it. Those levels are hardcoded below. Example: Wagail contains an IntranetUnitsPage for "Collections & Access - Access Services". The campus directory full name for Access Services should be "Access Services". """ titles = self._get_full_name_list() # Remove "container units". These are top-level units in the library's # system that aren't present in the campus directory. skip_containing_units = ['Collections & Access', 'Research & Learning'] for v in skip_containing_units: try: titles.remove(v) except ValueError: continue # Our system includes more than two levels of heiarchy, but the campus # directory only includes two. titles = titles[:2] return ' - '.join(titles) def get_context(self, request): context = super(IntranetUnitsPage, self).get_context(request) context['phone'] = '' if self.specific.internal_phone_number: context['phone'] = self.specific.internal_phone_number context['location'] = '' if self.specific.internal_location: context['location'] = self.specific.internal_location context['email'] = '' if self.specific.internal_email: context['email'] = self.specific.internal_email context['show_staff'] = self.show_staff context['show_departments'] = self.show_departments department_members = [] if self.specific.unit_page: unit_pages = self.specific.unit_page.get_descendants(True) staff_pages = StaffPage.objects.live().filter( staff_page_units__library_unit__in=unit_pages).distinct() # sorting: supervisors first, alphabetically; then non-supervisors, alphabetically. supervisor = self.specific.unit_page.department_head if supervisor: staff_pages = [supervisor] + list( staff_pages.exclude( pk=supervisor.pk).order_by('last_name')) else: staff_pages = list(staff_pages.order_by('last_name')) for staff_page in staff_pages: try: email = staff_page.staff_page_email.first().email except AttributeError: email = None phone_numbers = staff_page.staff_page_phone_faculty_exchange.all( ).values_list('phone_number', flat=True) titles = [] if staff_page.position_title: titles = [staff_page.position_title] department_members.append({ 'title': staff_page.title, 'url': staff_page.url, 'jobtitle': "<br/>".join(titles), 'email': email, 'phone': "<br/>".join(phone_numbers), }) context['department_members'] = department_members department_units = [] try: for unit_page in self.unit_page.get_descendants(): intranet_unit_page = unit_page.specific.loop_page.live( ).filter(show_in_menus=True).first() if intranet_unit_page: unit = { 'title': intranet_unit_page.title, 'url': intranet_unit_page.url, 'location': intranet_unit_page.internal_location, 'phone_number': intranet_unit_page.internal_phone_number, 'email': intranet_unit_page.internal_email } supervisors = [] if unit_page.specific.department_head: try: email = unit_page.specific.department_head.staff_page_email.first( ).email except AttributeError: email = '' try: phone_number = unit_page.specific.department_head.staff_page_phone_faculty_exchange.first( ).phone_number except AttributeError: phone_number = '' supervisors.append({ 'title': unit_page.specific.department_head.title, 'url': unit_page.specific.department_head.url, 'phone_number': phone_number, 'email': email }) unit['supervisors'] = supervisors department_units.append(unit) except (AttributeError): pass # split the department units into lists of lists, each inner list containing 4 or less items. context['department_unit_rows'] = [ department_units[i:i + 4] for i in range(0, len(department_units), 4) ] # reports tmp = [] unit_reports_pages = IntranetUnitsReportsPage.objects.descendant_of( self) for unit_reports_page in unit_reports_pages: for r in unit_reports_page.intranet_units_reports.all(): try: if not r.link and not r.document.url: continue except AttributeError: continue report = { 'summary': r.summary, 'date': r.date.strftime("%b. %-d, %Y"), 'sortdate': r.date.strftime("%Y%m%d") } if r.link: report['url'] = r.link elif r.document.url: report['url'] = r.document.url tmp.append(report) reports = sorted(tmp, key=lambda r: r['sortdate'], reverse=True)[:3] context['reports'] = reports return context
class StandardPage(PublicBasePage, SocialMediaFields): """ A standard basic page. """ # Page content body = StreamField(DefaultBodyFields(), blank=True) # Search widget enable_search_widget = models.BooleanField(default=False) # Find spaces fields enable_find_spaces = models.BooleanField(default=False) book_a_room_link = models.URLField(max_length=255, blank=True, default='') # Custom icons fields widget_title = models.CharField(max_length=100, blank=True) more_icons_link = models.URLField(max_length=255, blank=True, default='', verbose_name='View More Link') more_icons_link_label = models.CharField( max_length=100, blank=True, verbose_name='View More Link Label') # Featured collections collection_page = models.ForeignKey('lib_collections.CollectionPage', null=True, blank=True, related_name='+', on_delete=models.SET_NULL) # Featured Library Expert featured_library_expert_fallback = StreamField( FeaturedLibraryExpertBaseFields(required=False), blank=True, default=[]) expert_link = models.CharField(max_length=400, default="/about/directory/?view=staff", verbose_name="Featured Expert Link") featured_library_experts = StreamField( FeaturedLibraryExpertFields(required=False), blank=True, default=[]) subpage_types = [ 'alerts.AlertIndexPage', 'public.StandardPage', 'public.LocationPage', 'public.DonorPage', 'lib_collections.CollectingAreaPage', 'lib_collections.CollectionPage', 'lib_collections.ExhibitPage', 'lib_news.LibNewsIndexPage', 'redirects.RedirectPage', 'units.UnitPage', 'ask_a_librarian.AskPage', 'units.UnitIndexPage', 'conferences.ConferenceIndexPage', 'base.IntranetPlainPage', 'dirbrowse.DirBrowsePage', 'public.StaffPublicPage', ] content_panels = Page.content_panels + [ StreamFieldPanel('body'), ] + PublicBasePage.content_panels widget_content_panels = [ MultiFieldPanel([FieldPanel('enable_search_widget')], heading='Search Widget'), MultiFieldPanel([ FieldPanel('quicklinks_title'), FieldPanel('quicklinks'), FieldPanel('view_more_link_label'), FieldPanel('view_more_link'), FieldPanel('change_to_callout'), ], heading='Quicklinks'), MultiFieldPanel([ FieldPanel('enable_index'), FieldPanel('display_hierarchical_listing'), ], heading='Auto-generated Sitemap'), MultiFieldPanel([ FieldPanel('display_hours_in_right_sidebar'), ], heading='Granular hours'), MultiFieldPanel([ ImageChooserPanel('banner_image'), FieldPanel('banner_title'), ], heading='Banner'), MultiFieldPanel([ FieldPanel('events_feed_url'), ], heading='Workshops and Events'), MultiFieldPanel([ FieldPanel('news_feed_source'), FieldPanel('external_news_page'), PageChooserPanel('internal_news_page'), ], heading='News'), MultiFieldPanel([ FieldPanel('enable_find_spaces'), FieldPanel('book_a_room_link'), ], heading='Find Spaces'), MultiFieldPanel([ PageChooserPanel('collection_page', 'lib_collections.CollectionPage'), ], heading='Featured Collection'), MultiFieldPanel([ FieldPanel('rich_text_heading'), FieldPanel('rich_text'), PageChooserPanel('rich_text_link'), FieldPanel('rich_text_external_link'), FieldPanel('rich_text_link_text'), ], heading='Rich Text'), InlinePanel('carousel_items', label='Carousel items'), MultiFieldPanel([ FieldPanel('widget_title'), InlinePanel('icon_link_items', max_num=3, label='Icon Link items'), FieldPanel('more_icons_link'), FieldPanel('more_icons_link_label'), ], heading='Custom Icon Links'), InlinePanel('reusable_content', label='Reusable Content Blocks'), FieldPanel('expert_link'), StreamFieldPanel('featured_library_expert_fallback'), StreamFieldPanel('featured_library_experts'), MultiFieldPanel([ FieldPanel('cgi_mail_form_thank_you_text'), FieldPanel('cgi_mail_form'), ], heading='CGIMail Form'), ] + SocialMediaFields.panels search_fields = PublicBasePage.search_fields + [ index.SearchField('body', partial_match=True), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(PublicBasePage.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings', classname="settings"), ObjectList(widget_content_panels, heading='Widgets'), ]) def streamblock_has_link(self, streamblock, field): """ Check that a streamfield block object has a either an internal or external link when base.models.LinkBlock is in use. Args: streamblock: streamfield block object, wagtail.core.blocks.stream_block.StreamValue.StreamChild. field: string field name that contains a ListBlock of LinkBlocks. """ block_list = streamblock.value.get(field) for block in block_list: val1 = block.get('link_text') val2 = block.get('link_external') val3 = block.get('link_page') if not (val1 and val2) and not (val1 and val3): return False return True def streamblock_has_all_fields(self, streamblock, field_list): """ Test to see if a streamfield block has a value for all fields in a given list. Args: streamblock: streamfield block object, wagtail.core.blocks.stream_block.StreamValue.StreamChild. field_names: list of strings, field names. Returns: Boolean """ for field in field_list: value = streamblock.value.get(field) if not value: return False return True def has_featured_lib_expert_fallback(self): """ Test to see if a page has a "Featured Library Expert" fallback set. Returns: Boolean """ try: return self.streamblock_has_all_fields( self.featured_library_expert_fallback[0], ['library_expert']) and self.streamblock_has_link( self.featured_library_expert_fallback[0], 'libguides') except (IndexError): return False def get_featured_lib_expert(self): """ Test to see if a page has a "Featured Library Expert". Return a boolean and the featured library expert for today's date. Return False and None if there is no Featured Library Expert for today. The fallback is used if nothing is available for today's date. In order to return True a proper fallback must always be set. Returns: A mixed tuple where the first value is a boolean and the second value is a streamfield block or None when the first value is False. """ fallback = self.has_featured_lib_expert_fallback() # print(self.featured_library_expert_fallback[0].value.get('library_expert')) today = date.today() for block in self.featured_library_experts: # print(block.value.get('library_expert')) has_fields = self.streamblock_has_all_fields( block, ['library_expert', 'start_date', 'end_date']) # Could misfire, just an estimation has_links = self.streamblock_has_link(block, 'libguides') in_range = block.value.get( 'start_date') <= today and block.value.get('end_date') >= today if (fallback and (has_fields and has_links)) and in_range: return (True, block) if (fallback): return (True, self.featured_library_expert_fallback[0]) return (False, None) def unpack_lib_expert_block(self, block, current_site): """ Unpack the values from a "Featured Library Expert" streamfield block and return a data structure for display in the templates. This method wouldn't be needed, however, at the moment Wagtail doesn't allow for getting the page context from a block. This is discussed in Wagtail github issue #s 1743 and 2469. When a solution is implemented in the Wagtail base we could get rid of this. Args: block: Featured Library Expert or fallback streamfield block. current_site: Wagtail site object for the request. Returns: Mixed dictionary with the following values: person (StaffPage object), image (object), profile (string url), links (list of html strings). """ person = block.value.get('library_expert') libguides = block.value.get('libguides') image = person.specific.profile_picture email = person.specific.staff_page_email.first().email try: public_person = StaffPublicPage.objects.get(title=str(person)) except: public_person = None profile = public_person.relative_url( current_site) if public_person else None links = [] for guide in libguides: link_text = guide['link_text'] url = guide['link_external'] if guide['link_external'] else guide[ 'link_page'].relative_url(current_site) html = '<a href="%s">%s</a>' % (url, link_text) links.append(html) return { 'person': person, 'image': image, 'profile': profile, 'links': links, 'email': email } @property def has_find_spaces(self): """ Determine if there is a "Find Spaces" widget on the page. Returns: Boolean """ return self.has_field([self.enable_find_spaces]) @property def has_icon_link_items(self): """ Determine if there is a "Link Items" widget on the page. Returns: Boolean """ if self.has_field([self.icon_link_items]): return self.icon_link_items.all().count() > 0 return False @property def has_right_sidebar(self): """ Determine if a right sidebar should be displayed in the template. Returns: boolean """ fields = [self.collection_page] if self.base_has_right_sidebar(): return True elif self.has_social_media: return True elif self.has_find_spaces: return True else: return self.has_field(fields) def get_context(self, request): """ Override the page object's get context method. """ context = super(StandardPage, self).get_context(request) current_site = Site.find_for_request(request) has_featured_lib_expert = self.get_featured_lib_expert()[0] if has_featured_lib_expert: lib_expert_block = self.unpack_lib_expert_block( self.get_featured_lib_expert()[1], current_site) has_libcal_schedule = libcal_id_by_email( lib_expert_block['email']) != '' context['has_featured_lib_expert'] = has_featured_lib_expert context['has_libcal_schedule'] = has_libcal_schedule context['featured_lib_expert'] = self.get_featured_lib_expert()[1] context['featured_lib_expert_name'] = lib_expert_block['person'] context['featured_lib_expert_image'] = lib_expert_block['image'] context['featured_lib_expert_profile'] = lib_expert_block[ 'profile'] context['featured_lib_expert_links'] = lib_expert_block['links'] context['email'] = lib_expert_block['email'] context['has_search_widget'] = self.enable_search_widget return context
class NewsPage(BasePage): """ News story content type used on intranet pages. """ excerpt = RichTextField( blank=True, null=True, help_text= 'Shown on the News feed. Populated automatically from “Body” if left empty.' ) author = models.ForeignKey('staff.StaffPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='news_stories') story_date = models.DateField( default=timezone.now, help_text= 'If you use Settings to publish a future post, put the publish date here. Otherwise, leave today as the story date.' ) sticky_until = models.DateField( blank=True, null=True, help_text='To be used by Admin and HR only.') thumbnail = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') alt_text = models.CharField(max_length=100, blank=True) body = StreamField(DefaultBodyFields(), blank=False, null=False) subpage_types = [] content_panels = Page.content_panels + [ StreamFieldPanel('body'), FieldPanel('author'), FieldPanel('story_date'), FieldPanel('sticky_until'), MultiFieldPanel( [ ImageChooserPanel('thumbnail'), FieldPanel('alt_text'), ], heading='Thumbnail', ), FieldPanel('excerpt'), ] + BasePage.content_panels search_fields = PublicBasePage.search_fields + [ index.SearchField('excerpt'), index.SearchField('author'), index.SearchField('thumbnail'), index.SearchField('body'), ] def get_context(self, request): context = super(NewsPage, self).get_context(request) details = get_story_summary(self) context['story_date'] = details['story_date'] try: context['author_title'] = details['author_title'] except: context['author_title'] = '' context['author_url'] = details['author_url'] context['thumbnail'] = details['thumbnail'] return context
class GroupPage(BasePage, Email): """ Content type for group and committee pages. """ subpage_types = [ 'base.IntranetIndexPage', 'base.IntranetPlainPage', 'group.GroupPage', 'group.GroupMeetingMinutesIndexPage', 'group.GroupReportsIndexPage', 'intranetforms.IntranetFormPage', 'projects.ProjectPage' ] meeting_location = CharField(blank=True, max_length=255) meeting_start_time = models.TimeField(auto_now=False, auto_now_add=False, default=timezone.now, blank=True, null=True) meeting_end_time = models.TimeField(auto_now=False, auto_now_add=False, default=default_end_time, blank=True, null=True) meeting_frequency = CharField(blank=True, max_length=255) intro = StreamField(DefaultBodyFields(), blank=True) is_active = models.BooleanField(default=True) body = StreamField(DefaultBodyFields()) content_panels = Page.content_panels + Email.content_panels + [ MultiFieldPanel([ FieldPanel('meeting_start_time'), FieldPanel('meeting_end_time'), FieldPanel('meeting_location'), FieldPanel('meeting_frequency'), ], heading='Meeting Information'), StreamFieldPanel('intro'), InlinePanel('group_members', label='Group Members'), FieldPanel('is_active'), StreamFieldPanel('body'), ] + BasePage.content_panels search_fields = BasePage.search_fields + [ index.SearchField('meeting_location'), index.SearchField('meeting_frequency'), index.SearchField('intro'), index.SearchField('body'), ] def get_context(self, request): context = super(GroupPage, self).get_context(request) group_members = self.group_members.all() # sorting: chairs or co-chairs first, alphabetically; then others, alphabetically. group_member_chairs = [] non_group_member_chairs = [] for g in group_members: if g.group_member == None: continue if g.group_member.live == False: continue if g.role and g.role.text in ['Chair', 'Co-Chair']: group_member_chairs.append(g) else: non_group_member_chairs.append(g) group_member_chairs = sorted(group_member_chairs, key=lambda g: g.group_member.title) non_group_member_chairs = sorted(non_group_member_chairs, key=lambda g: g.group_member.title) group_members = group_member_chairs + non_group_member_chairs # minutes tmp = [] group_meeting_min_pages = GroupMeetingMinutesPage.objects.descendant_of( self) for group_meeting_min_page in group_meeting_min_pages: for m in group_meeting_min_page.meeting_minutes.all(): try: if not m.link and not m.document.url: continue except AttributeError: continue minute = { 'summary': m.summary, 'date': m.date.strftime("%b. %-d, %Y"), 'sortdate': m.date.strftime("%Y%m%d") } if m.link: minute['url'] = m.link elif m.document.url: minute['url'] = m.document.url tmp.append(minute) minutes = sorted(tmp, key=lambda m: m['sortdate'], reverse=True)[:3] # reports tmp = [] group_reports_pages = GroupReportsPage.objects.descendant_of(self) for group_reports_page in group_reports_pages: for r in group_reports_page.group_reports.all(): try: if not r.link and not r.document.url: continue except AttributeError: continue report = { 'summary': r.summary, 'date': r.date.strftime("%b. %-d, %Y"), 'sortdate': r.date.strftime("%Y%m%d") } if r.link: report['url'] = r.link elif r.document.url: report['url'] = r.document.url tmp.append(report) reports = sorted(tmp, key=lambda r: r['sortdate'], reverse=True)[:3] context['minutes'] = minutes context['reports'] = reports context['group_members'] = list( map( lambda m: { 'title': m.group_member.title, 'unit': '<br/>'.join( sorted( map(lambda u: u.library_unit.get_full_name(), m.group_member.staff_page_units.all()))), 'url': m.group_member.url, 'role': m.role }, group_members)) return context
class StaffPage(BasePageWithoutStaffPageForeignKeys): """ Staff profile content type. """ cnetid = CharField( max_length=255, blank=False) display_name = CharField( max_length=255, null=True, blank=True) official_name = CharField( max_length=255, null=True, blank=True) first_name = CharField( max_length=255, null=True, blank=True) middle_name = CharField( max_length=255, null=True, blank=True) last_name = CharField( max_length=255, null=True, blank=True) supervisor = models.ForeignKey( 'staff.StaffPage', null=True, blank=True, on_delete=models.SET_NULL) profile_picture = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') libguide_url = models.URLField( max_length=255, null=True, blank=True) bio = StreamField(DefaultBodyFields(), blank=True, null=True) cv = models.ForeignKey( 'wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) is_public_persona = BooleanField(default=False) orcid_regex = RegexValidator(regex=ORCID_FORMAT, message=ORCID_ERROR_MSG) orcid = CharField( max_length=255, null=True, blank=True, validators=[orcid_regex] ) objects = StaffPageManager() @property def get_staff_subjects(self): """ Get the subjects beloning to the staff member - UNTESTED """ return get_public_profile('elong') @property def is_subject_specialist(self): """ See if the staff member is a subject specialist - PLACEHOLDER """ subjects = self.get_subjects() return None @property def public_page(self): """ Get a public staff profile page for the library expert if one exists. """ from public.models import StaffPublicPage # Should try to do better try: return StaffPublicPage.objects.live().filter(title=self.cnetid)[0] except(IndexError): return None def get_subjects(self): """ Get all the subjects for a staff member. Must return a string for elasticsearch. Returns: String, concatenated list of subjects. """ subject_list = self.staff_subject_placements.values_list('subject', flat=True) return '\n'.join(Subject.objects.get(id=subject).name for subject in subject_list) content_panels = Page.content_panels + [ ImageChooserPanel('profile_picture'), StreamFieldPanel('bio'), DocumentChooserPanel('cv'), FieldPanel('libguide_url'), FieldPanel('is_public_persona'), InlinePanel('staff_subject_placements', label='Subject Specialties'), InlinePanel('expertise_placements', label='Expertise'), FieldPanel('orcid') ] + BasePageWithoutStaffPageForeignKeys.content_panels search_fields = BasePageWithoutStaffPageForeignKeys.search_fields + [ index.SearchField('profile_picture'), index.SearchField('cv'), index.SearchField('libguide_url'), index.SearchField('orcid'), index.SearchField('get_subjects') ] subpage_types = ['base.IntranetIndexPage', 'base.IntranetPlainPage', 'intranetforms.IntranetFormPage', 'intranettocs.TOCPage'] class Meta: ordering = ['last_name', 'first_name'] def get_context(self, request): vcard_titles = set() faculty_exchanges = set() emails = set() phones = set() units = set() for vcard in self.vcards.all(): vcard_titles.add(re.sub('\s+', ' ', vcard.title).strip()) faculty_exchanges.add(re.sub('\s+', ' ', vcard.faculty_exchange).strip()) emails.add(vcard.email) phones.add(vcard.phone_number) try: unit_title = vcard.unit.fullName except: unit_title = None try: unit_url = vcard.unit.intranet_unit_page.first().url except: unit_url = None units.add(json.dumps({ 'title': unit_title, 'url': unit_url })) vcard_titles = list(vcard_titles) faculty_exchanges = list(faculty_exchanges) emails = list(emails) phones = list(phones) units = list(map(json.loads, list(units))) subjects = [] for subject in self.staff_subject_placements.all(): subjects.append({ 'name': subject.subject.name, 'url': '' }) group_memberships = [] for group_membership in self.member.all(): if group_membership.parent.is_active: group_memberships.append({ 'group': { 'title': group_membership.parent.title, 'url': group_membership.parent.url }, 'role': group_membership.role }) context = super(StaffPage, self).get_context(request) context['vcard_titles'] = vcard_titles context['faculty_exchanges'] = faculty_exchanges context['emails'] = emails context['phones'] = phones context['units'] = units context['subjects'] = subjects context['group_memberships'] = group_memberships return context