diff --git a/network-api/networkapi/mozfest/migrations/0022_body_tabbed_profile_directory_block.py b/network-api/networkapi/mozfest/migrations/0022_body_tabbed_profile_directory_block.py new file mode 100644 index 00000000000..07d4aa006e5 --- /dev/null +++ b/network-api/networkapi/mozfest/migrations/0022_body_tabbed_profile_directory_block.py @@ -0,0 +1,24 @@ +# Generated by Django 3.1.11 on 2021-10-11 07:15 + +from django.db import migrations +import networkapi.wagtailpages.pagemodels.blog.blog_category +import wagtail.core.blocks +import wagtail.core.blocks.static_block +import wagtail.core.fields +import wagtail.images.blocks +import wagtail.snippets.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('mozfest', '0021_add_spaces_cards'), + ] + + operations = [ + migrations.AlterField( + model_name='mozfestprimarypage', + name='body', + field=wagtail.core.fields.StreamField([('paragraph', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'large', 'h2', 'h3', 'h4', 'h5', 'ol', 'ul', 'link', 'hr'], template='wagtailpages/blocks/rich_text_block.html')), ('card_grid', wagtail.core.blocks.StructBlock([('cards', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('alt_text', wagtail.core.blocks.CharBlock(help_text="Alt text for card's image.", required=False)), ('title', wagtail.core.blocks.CharBlock(help_text='Heading for the card.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('link_url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this card should link out to. (Note: If left blank, link will not render.) ', required=False)), ('link_label', wagtail.core.blocks.CharBlock(help_text='Optional Label for the URL link above. (Note: If left blank, link will not render.) ', required=False))]), help_text='Please use a minimum of 2 cards.'))])), ('image', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL that this caption should link out to.', required=False)), ('image_width', wagtail.core.blocks.ChoiceBlock(choices=[('normal', 'Normal'), ('wide', 'Wide'), ('full_width', 'Full Width')], help_text='Wide images are col-12, Full-Width Images reach both ends of the screen (16:6 images recommended for full width)'))])), ('image_text', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'link'])), ('url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this image should link out to.', required=False)), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False))])), ('image_text_mini', wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('altText', wagtail.core.blocks.CharBlock(help_text='Image description (for screen readers).', required=True)), ('text', wagtail.core.blocks.RichTextBlock(features=['bold', 'italic', 'link']))])), ('image_grid', wagtail.core.blocks.StructBlock([('grid_items', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('alt_text', wagtail.core.blocks.CharBlock(help_text='Alt text for this image.', required=False)), ('caption', wagtail.core.blocks.CharBlock(help_text='Please remember to properly attribute any images we use.', required=False)), ('url', wagtail.core.blocks.CharBlock(help_text='Optional URL that this figure should link out to.', required=False)), ('square_image', wagtail.core.blocks.BooleanBlock(default=True, help_text='If left checked, the image will be cropped to be square.', required=False))])))])), ('video', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.CharBlock(help_text='For YouTube: go to your YouTube video and click “Share,” then “Embed,” and then copy and paste the provided URL only. For example: https://www.youtube.com/embed/3FIVXBawyQw For Vimeo: follow similar steps to grab the embed URL. For example: https://player.vimeo.com/video/9004979')), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL for caption to link to.', required=False)), ('video_width', wagtail.core.blocks.ChoiceBlock(choices=[('normal', 'Normal'), ('wide', 'Wide'), ('full_width', 'Full Width')], help_text='Wide videos are col-12, Full-Width videos reach both ends of the screen.'))])), ('iframe', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.CharBlock(help_text='Please note that only URLs from allow-listed domains will work.')), ('height', wagtail.core.blocks.IntegerBlock(help_text='Optional integer pixel value for custom iFrame height', required=False)), ('caption', wagtail.core.blocks.CharBlock(required=False)), ('captionURL', wagtail.core.blocks.CharBlock(help_text='Optional URL that this caption should link out to.', required=False)), ('iframe_width', wagtail.core.blocks.ChoiceBlock(choices=[('normal', 'Normal'), ('wide', 'Wide'), ('full_width', 'Full Width')], help_text='Wide iframes are col-12, Full-Width iframes reach both ends of the screen'))])), ('linkbutton', wagtail.core.blocks.StructBlock([('label', wagtail.core.blocks.CharBlock()), ('URL', wagtail.core.blocks.CharBlock()), ('styling', wagtail.core.blocks.ChoiceBlock(choices=[('btn-primary', 'Primary button'), ('btn-secondary', 'Secondary button')]))])), ('spacer', wagtail.core.blocks.StructBlock([('size', wagtail.core.blocks.ChoiceBlock(choices=[('1', 'quarter spacing'), ('2', 'half spacing'), ('3', 'single spacing'), ('4', 'one and a half spacing'), ('5', 'triple spacing')]))])), ('quote', wagtail.core.blocks.StructBlock([('quotes', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('quote', wagtail.core.blocks.CharBlock()), ('attribution', wagtail.core.blocks.CharBlock(required=False))])))])), ('pulse_listing', wagtail.core.blocks.StructBlock([('search_terms', wagtail.core.blocks.CharBlock(help_text='Test your search at mozillapulse.org/search', label='Search', required=False)), ('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=6, help_text='Choose 1-12. If you want visitors to see more, link to a search or tag on Pulse.', max_value=12, min_value=0, required=True)), ('only_featured_entries', wagtail.core.blocks.BooleanBlock(default=False, help_text='Featured items are selected by Pulse moderators.', label='Display only featured entries', required=False)), ('newest_first', wagtail.core.blocks.ChoiceBlock(choices=[('True', 'Show newer entries first'), ('False', 'Show older entries first')], label='Sort')), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('issues', wagtail.core.blocks.ChoiceBlock(choices=[('all', 'All'), ('Decentralization', 'Decentralization'), ('Digital Inclusion', 'Digital Inclusion'), ('Online Privacy & Security', 'Online Privacy & Security'), ('Open Innovation', 'Open Innovation'), ('Web Literacy', 'Web Literacy')])), ('help', wagtail.core.blocks.ChoiceBlock(choices=[('all', 'All'), ('Attend', 'Attend'), ('Create content', 'Create content'), ('Code', 'Code'), ('Design', 'Design'), ('Fundraise', 'Fundraise'), ('Join community', 'Join community'), ('Localize & translate', 'Localize & translate'), ('Mentor', 'Mentor'), ('Plan & organize', 'Plan & organize'), ('Promote', 'Promote'), ('Take action', 'Take action'), ('Test & feedback', 'Test & feedback'), ('Write documentation', 'Write documentation')], label='Type of help needed')), ('direct_link', wagtail.core.blocks.BooleanBlock(default=False, help_text='Checked: user goes to project link. Unchecked: user goes to pulse entry', label='Direct link', required=False))])), ('profile_listing', wagtail.core.blocks.StructBlock([('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=12, help_text='Pick up to 48 profiles.', max_value=48, min_value=1, required=True)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False))])), ('profile_by_id', wagtail.core.blocks.StructBlock([('ids', wagtail.core.blocks.CharBlock(help_text='Show profiles for pulse users with specific profile ids (mozillapulse.org/profile/[##]). For multiple profiles, specify a comma separated list (e.g. 85,105,332).', label='Profile by ID'))])), ('profile_directory', wagtail.core.blocks.StructBlock([('max_number_of_results', wagtail.core.blocks.IntegerBlock(default=12, help_text='Pick up to 48 profiles.', max_value=48, min_value=1, required=True)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', label=' ')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False)), ('filter_values', wagtail.core.blocks.CharBlock(default='2019,2018,2017,2016,2015,2014,2013', help_text='Example: 2019,2018,2017,2016,2015,2014,2013', required=True))])), ('recent_blog_entries', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock(required=False)), ('tag_filter', wagtail.core.blocks.CharBlock(help_text='Test this filter at foundation.mozilla.org/blog/tags/', label='Filter by Tag', required=False)), ('category_filter', wagtail.core.blocks.ChoiceBlock(choices=networkapi.wagtailpages.pagemodels.blog.blog_category.BlogPageCategory.get_categories, help_text='Test this filter at foundation.mozilla.org/blog/category/', label='Filter by Category', required=False)), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False))])), ('blog_set', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('top_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider above content block.', required=False)), ('bottom_divider', wagtail.core.blocks.BooleanBlock(help_text='Optional divider below content block.', required=False)), ('blog_pages', wagtail.core.blocks.ListBlock(wagtail.core.blocks.PageChooserBlock(page_type=['wagtailpages.BlogPage'])))])), ('airtable', wagtail.core.blocks.StructBlock([('url', wagtail.core.blocks.URLBlock(help_text="Copied from the Airtable embed code. The word 'embed' will be in the url")), ('height', wagtail.core.blocks.IntegerBlock(default=533, help_text='The pixel height on desktop view, usually copied from the Airtable embed code'))])), ('typeform', wagtail.core.blocks.StructBlock([('embed_id', wagtail.core.blocks.CharBlock(help_text='The embed id of your Typeform page (e.g. if the form is on admin.typeform.com/form/e8zScc6t, the id will be: e8zScc6t)', required=True)), ('button_type', wagtail.core.blocks.ChoiceBlock(choices=[('btn-primary', 'Primary button'), ('btn-secondary', 'Secondary button')])), ('button_text', wagtail.core.blocks.CharBlock(help_text='This is a text prompt for users to open the typeform content', required=True))])), ('tabbed_profile_directory', wagtail.core.blocks.StructBlock([('tabs', wagtail.snippets.blocks.SnippetChooserBlock('wagtailpages.PulseFilter', help_text='The profile information to create tabs on. The first option in the snippet will be used as the initial filter.')), ('subfilters', wagtail.core.blocks.StreamBlock([('filter', wagtail.snippets.blocks.SnippetChooserBlock('wagtailpages.PulseFilter'))], max_num=1, required=False)), ('advanced_filter_header', wagtail.core.blocks.static_block.StaticBlock(admin_text='Note that the filter not be used if selected as the tabs filter or as one of the subfilters. For example, if the tabs filter profile types, the profile type field below will be ignored.', label='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------')), ('profile_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Fellow.', required=False)), ('program_type', wagtail.core.blocks.CharBlock(default='', help_text='Example: Tech Policy.', required=False)), ('year', wagtail.core.blocks.CharBlock(default='', required=False))])), ('space_card_list', wagtail.core.blocks.StructBlock([('title', wagtail.core.blocks.CharBlock()), ('space_cards', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.core.blocks.CharBlock(help_text='Heading for the card.')), ('body', wagtail.core.blocks.TextBlock(help_text='Body text of the card.')), ('link', wagtail.core.blocks.StreamBlock([('internal', wagtail.core.blocks.StructBlock([('link', wagtail.core.blocks.PageChooserBlock(help_text='Page that this should link out to.'))])), ('external', wagtail.core.blocks.StructBlock([('link', wagtail.core.blocks.URLBlock(help_text='URL that this should link out to.'))]))], help_text='Page or external URL this card will link out to.', max_num=1))]), help_text='A list of Space Cards.'))]))]), + ), + ] diff --git a/network-api/networkapi/mozfest/models.py b/network-api/networkapi/mozfest/models.py index 6691e4bf3ac..763ab8a44f5 100644 --- a/network-api/networkapi/mozfest/models.py +++ b/network-api/networkapi/mozfest/models.py @@ -6,19 +6,16 @@ from wagtail.snippets.edit_handlers import SnippetChooserPanel from wagtail_localize.fields import SynchronizedField, TranslatableField - from networkapi.wagtailpages.utils import ( set_main_site_nav_information, get_page_tree_information ) - from networkapi.wagtailpages.models import ( base_fields, FoundationMetadataPageMixin, FoundationBannerInheritanceMixin, Signup ) - from networkapi.wagtailpages.pagemodels import customblocks @@ -54,6 +51,7 @@ class MozfestPrimaryPage(FoundationMetadataPageMixin, FoundationBannerInheritanc body = StreamField( base_fields + [ + ('tabbed_profile_directory', customblocks.TabbedProfileDirectory()), ('space_card_list', customblocks.SpaceCardListBlock()), ] ) diff --git a/network-api/networkapi/wagtailpages/migrations/0037_pulsefilter_pulsefilteroption.py b/network-api/networkapi/wagtailpages/migrations/0037_pulsefilter_pulsefilteroption.py new file mode 100644 index 00000000000..8b51c3281b9 --- /dev/null +++ b/network-api/networkapi/wagtailpages/migrations/0037_pulsefilter_pulsefilteroption.py @@ -0,0 +1,37 @@ +# Generated by Django 3.1.11 on 2021-10-07 13:36 + +from django.db import migrations, models +import django.db.models.deletion +import modelcluster.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailpages', '0036_blogpagecategory_title'), + ] + + operations = [ + migrations.CreateModel( + name='PulseFilter', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Identify this filter for other editors.', max_length=255, unique=True)), + ('filter_key', models.CharField(choices=[('profile_type', 'Profile Type'), ('program_type', 'Program Type'), ('program_year', 'Program Year')], help_text='The profile detail to filter on.', max_length=255)), + ('filter_key_label', models.CharField(help_text='A label for when displaying the filter type to users. (e.g. "Spaces" for program types)', max_length=255)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='PulseFilterOption', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('filter_value', models.CharField(help_text='The exact value to filter by in the directory; e.g. "staff", "mozfest ambassador", "mozfest wrangler" for profile types.', max_length=255)), + ('filter_label', models.CharField(help_text='The label to display on the tabs; e.g. "Facilitators", "Ambassadors", "Wranglers" for profile types.', max_length=255)), + ('enable_subfiltering', models.BooleanField(blank=True, default=True, help_text='Display additional filtering options if available.')), + ('pulse_filter', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='wagtailpages.pulsefilter')), + ], + ), + ] diff --git a/network-api/networkapi/wagtailpages/models.py b/network-api/networkapi/wagtailpages/models.py index 33614ed0a41..50c63cfbb42 100644 --- a/network-api/networkapi/wagtailpages/models.py +++ b/network-api/networkapi/wagtailpages/models.py @@ -96,6 +96,8 @@ SoftwareProductPage, ) +from .pagemodels.pulse import PulseFilter + __all__ = [ ArticlePage, @@ -135,6 +137,7 @@ ProductPageCategory, ProductPagePrivacyPolicyLink, PublicationPage, + PulseFilter, RedirectingPage, Signup, SoftwareProductPage, diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py index eefdbdf9d6c..f18072a1440 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/__init__.py @@ -14,7 +14,7 @@ from .link_button_block import LinkButtonBlock from .looping_video_block import LoopingVideoBlock from .profile_by_id import ProfileById -from .profile_directory import ProfileDirectory +from .profile_directory import ProfileDirectory, TabbedProfileDirectory from .pulse_project_list import PulseProjectList from .recent_blog_entries import RecentBlogEntries from .typeform_block import TypeformBlock @@ -57,6 +57,7 @@ SingleQuoteBlock, SpaceCardListBlock, RecentBlogEntries, + TabbedProfileDirectory, TypeformBlock, VideoBlock, YoutubeRegretBlock, diff --git a/network-api/networkapi/wagtailpages/pagemodels/customblocks/profile_directory.py b/network-api/networkapi/wagtailpages/pagemodels/customblocks/profile_directory.py index dc2eddc5b27..0526f1e861b 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/customblocks/profile_directory.py +++ b/network-api/networkapi/wagtailpages/pagemodels/customblocks/profile_directory.py @@ -1,5 +1,15 @@ +import json +from urllib import request, parse + from django.conf import settings +from django.core.exceptions import ValidationError +from django.forms.utils import ErrorList + from wagtail.core import blocks +from wagtail.core.blocks.stream_block import StreamBlockValidationError +from wagtail.core.blocks.struct_block import StructBlockValidationError +from wagtail.snippets.blocks import SnippetChooserBlock + from .latest_profile_list import LatestProfileList @@ -45,3 +55,133 @@ def get_context(self, value, parent_context=None): class Meta: template = 'wagtailpages/blocks/profile_directory.html' icon = 'group' + + +class TabbedProfileDirectory(blocks.StructBlock): + """ + This component is set up to create a directory with tabs. The tabs are filtered on a + specific profile field (e.g. profile_type). + https://github.com/mozilla/foundation.mozilla.org/issues/7422 + """ + tabs = SnippetChooserBlock( + 'wagtailpages.PulseFilter', + help_text=( + 'The profile information to create tabs on. The first option in the snippet' + ' will be used as the initial filter.' + ), + ) + subfilters = blocks.StreamBlock( + [ + ('filter', SnippetChooserBlock('wagtailpages.PulseFilter')), + ], + max_num=1, + required=False, + ) + + advanced_filter_header = blocks.StaticBlock( + label='-------- ADVANCED FILTERS: OPTIONS TO DISPLAY FEWER, MORE TARGETED RESULTS. --------', + admin_text=( + 'Note that the filter not be used if selected as the tabs filter or as one of the subfilters.' + ' For example, if the tabs filter profile types, the profile type field below will be ignored.' + ), + ) + + profile_type = blocks.CharBlock( + required=False, + default='', + help_text='Example: Fellow.' + ) + program_type = blocks.CharBlock( + required=False, + default='', + help_text='Example: Tech Policy.' + ) + year = blocks.CharBlock( + required=False, + default='' + ) + + class Meta: + template = 'wagtailpages/blocks/tabbed_profile_directory.html' + icon = 'group' + + def clean(self, value): + result = super().clean(value) + errors = {} + + # Check if any of the subfilter snippets are the same as the tabs snippet. + for index, subfilter_block in enumerate(value['subfilters']): + if subfilter_block.value.pk == value['tabs'].pk: + errors['subfilters'] = ErrorList([ + StreamBlockValidationError( + block_errors={ + index: ErrorList( + [ + ValidationError(ErrorList(['The subfilter cannot be the same as the tabs.'])), + ] + ) + }, + non_block_errors=ErrorList(), + ) + ]) + + if errors: + raise StructBlockValidationError(errors) + + return result + + def get_context(self, value, parent_context=None, ordering=False): + context = super().get_context(value, parent_context=parent_context) + pulse_api = settings.FRONTEND['PULSE_API_DOMAIN'] + + query_args = { + 'profile_type': value['profile_type'], + 'program_type': value['program_type'], + 'program_year': value['year'], + 'ordering': ordering if ordering else 'custom_name', + 'is_active': 'true', + 'format': 'json', + } + + # Use first tabs filter option as the initial filter. + tabs = value['tabs'] + query_args[tabs.filter_key] = tabs.options.first().filter_value + + # Remove specific filter if the filter is in use for subfilters. + for subfilter_block in value['subfilters']: + filter_key = subfilter_block.value.filter_key + if filter_key in query_args: + query_args.pop(filter_key) + + # Filter out emptish values + query_args = {k: v for k, v in query_args.items() if v} + + url = '{pulse_api}/api/pulse/v2/profiles/?{query}'.format( + pulse_api=pulse_api, + query=parse.urlencode(query_args) + ) + + data = [] + + try: + response = request.urlopen(url) + response_data = response.read() + data = json.loads(response_data) + + for profile in data: + profile['created_entries'] = False + profile['published_entries'] = False + profile['entry_count'] = False + profile['user_bio_long'] = False + + except (IOError, ValueError) as exception: + print(str(exception)) + pass + + context['profiles'] = data + context['profile_type'] = value['profile_type'] + context['program_type'] = value['program_type'] + context['program_year'] = value['year'] + context['api_endpoint'] = f"{pulse_api}/api/pulse/v2/profiles/?ordering=custom_name&is_active=true&format=json" + + return context diff --git a/network-api/networkapi/wagtailpages/pagemodels/pulse.py b/network-api/networkapi/wagtailpages/pagemodels/pulse.py new file mode 100644 index 00000000000..652840a1c58 --- /dev/null +++ b/network-api/networkapi/wagtailpages/pagemodels/pulse.py @@ -0,0 +1,73 @@ +from django.db import models + +from modelcluster.fields import ParentalKey +from modelcluster.models import ClusterableModel +from wagtail.admin.edit_handlers import FieldPanel, InlinePanel +from wagtail.snippets.models import register_snippet + + +@register_snippet +class PulseFilter(ClusterableModel): + name = models.CharField( + help_text='Identify this filter for other editors.', + max_length=255, + unique=True, + ) + filter_key = models.CharField( + choices=[ + ('profile_type', 'Profile Type'), + ('program_type', 'Program Type'), + ('program_year', 'Program Year'), + ], + help_text='The profile detail to filter on.', + max_length=255, + ) + filter_key_label = models.CharField( + help_text='A label for when displaying the filter type to users. (e.g. "Spaces" for program types)', + max_length=255, + ) + + panels = [ + FieldPanel('name'), + FieldPanel('filter_key'), + FieldPanel('filter_key_label'), + InlinePanel('options', label='Options', min_num=1), + ] + + def __str__(self): + return self.name + + +class PulseFilterOption(models.Model): + pulse_filter = ParentalKey( + 'wagtailpages.PulseFilter', + related_name='options', + ) + filter_value = models.CharField( + help_text=( + 'The exact value to filter by in the directory;' + ' e.g. "staff", "mozfest ambassador", "mozfest wrangler" for profile types.' + ), + max_length=255, + ) + filter_label = models.CharField( + help_text=( + 'The label to display on the tabs;' + ' e.g. "Facilitators", "Ambassadors", "Wranglers" for profile types.' + ), + max_length=255, + ) + enable_subfiltering = models.BooleanField( + blank=True, + default=True, + help_text='Display additional filtering options if available.', + ) + + panels = [ + FieldPanel('filter_value'), + FieldPanel('filter_label'), + FieldPanel('enable_subfiltering'), + ] + + def __str__(self): + return self.filter_label diff --git a/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/tabbed_profile_directory.html b/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/tabbed_profile_directory.html new file mode 100644 index 00000000000..1ef11ec868e --- /dev/null +++ b/network-api/networkapi/wagtailpages/templates/wagtailpages/blocks/tabbed_profile_directory.html @@ -0,0 +1,47 @@ +{% extends "./base_streamfield_block.html" %} +{% load wagtailcore_tags static %} + +{% block block_content %} + {# Tabs #} +