def setUp(self): self.EventPageForm = get_form_for_model( EventPage, form_class=WagtailAdminPageForm, formsets=[]) self.event = EventPage(title='Abergavenny sheepdog trials', date_from=date(2014, 7, 20), date_to=date(2014, 7, 21)) self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage)
class TestFieldPanel(TestCase): class FakeClass(object): required = False widget = 'fake widget' class FakeField(object): label = 'label' help_text = 'help text' errors = ['errors'] id_for_label = 'id for label' class FakeForm(dict): def __init__(self, *args, **kwargs): self.fields = self.fields_iterator() def fields_iterator(self): for i in self: yield i def setUp(self): fake_field = self.FakeField() fake_field.field = self.FakeClass() self.field_panel = FieldPanel('barbecue', 'snowman')( instance=True, form={'barbecue': fake_field}) def test_render_as_object(self): result = self.field_panel.render_as_object() self.assertIn('<legend>label</legend>', result) self.assertIn('<p class="error-message">', result) def test_render_js_unknown_widget(self): field = self.FakeField() bound_field = self.FakeField() widget = self.FakeField() field.widget = widget bound_field.field = field self.field_panel.bound_field = bound_field result = self.field_panel.render_js() self.assertEqual(result, '') def test_render_as_field(self): field = self.FakeField() bound_field = self.FakeField() bound_field.field = field self.field_panel.bound_field = bound_field result = self.field_panel.render_as_field() self.assertIn('<p class="help">help text</p>', result) self.assertIn('<span>errors</span>', result) def test_rendered_fields(self): result = self.field_panel.rendered_fields() self.assertEqual(result, ['barbecue']) def test_field_type(self): fake_object = self.FakeClass() another_fake_object = self.FakeClass() fake_object.field = another_fake_object self.field_panel.bound_field = fake_object self.assertEqual(self.field_panel.field_type(), 'fake_class') def test_widget_overrides(self): result = FieldPanel('barbecue', 'snowman').widget_overrides() self.assertEqual(result, {}) def test_required_formsets(self): result = FieldPanel('barbecue', 'snowman').required_formsets() self.assertEqual(result, []) def test_get_form_class(self): result = FieldPanel('barbecue', 'snowman').get_form_class(Page) self.assertTrue(issubclass(result, WagtailAdminModelForm)) def test_render_js(self): result = self.field_panel.render_js() self.assertEqual(result, "") def test_render_missing_fields(self): fake_form = self.FakeForm() fake_form["foo"] = "bar" self.field_panel.form = fake_form self.assertEqual(self.field_panel.render_missing_fields(), "bar") def test_render_form_content(self): fake_form = self.FakeForm() fake_form["foo"] = "bar" self.field_panel.form = fake_form self.assertIn("bar", self.field_panel.render_form_content())
class AnswerPage(CFGOVPage): """ Page type for Ask CFPB answers. """ from ask_cfpb.models import Answer last_edited = models.DateField( blank=True, null=True, help_text="Change the date to today if you make a significant change.") question = models.TextField(blank=True) statement = models.TextField( blank=True, help_text=( "(Optional) Use this field to rephrase the question title as " "a statement. Use only if this answer has been chosen to appear " "on a money topic portal (e.g. /consumer-tools/debt-collection).")) answer = RichTextField(blank=True) snippet = RichTextField(blank=True, help_text='Optional answer intro') search_tags = models.CharField( max_length=1000, blank=True, help_text="PLEASE DON'T USE. THIS FIELD IS NOT YET ACTIVATED.") answer_base = models.ForeignKey( Answer, blank=True, null=True, related_name='answer_pages', on_delete=models.SET_NULL) redirect_to = models.ForeignKey( Answer, blank=True, null=True, on_delete=models.SET_NULL, related_name='redirected_pages', help_text="Choose another Answer to redirect this page to") content = StreamField([ ('feedback', v1_blocks.Feedback()), ], blank=True) content_panels = CFGOVPage.content_panels + [ MultiFieldPanel([ FieldRowPanel([FieldPanel('last_edited')]) ], heading="Visible time stamp"), FieldPanel('question'), FieldPanel('statement'), FieldPanel('snippet'), FieldPanel('answer'), FieldPanel('search_tags'), FieldPanel('redirect_to'), ] sidebar = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('related_metadata', molecules.RelatedMetadata()), ('email_signup', organisms.EmailSignUp()), ('sidebar_contact', organisms.SidebarContactInfo()), ('rss_feed', molecules.RSSFeed()), ('social_media', molecules.SocialMedia()), ('reusable_text', v1_blocks.ReusableTextChooserBlock(ReusableText)), ], blank=True) sidebar_panels = [StreamFieldPanel('sidebar'), ] search_fields = Page.search_fields + [ index.SearchField('answer'), index.SearchField('snippet') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(sidebar_panels, heading='Sidebar (English only)'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) objects = CFGOVPageManager() def get_context(self, request, *args, **kwargs): context = super(AnswerPage, self).get_context(request) context['answer_id'] = self.answer_base.id context['related_questions'] = self.answer_base.related_questions.all() context['description'] = self.snippet if self.snippet \ else Truncator(self.answer).words(40, truncate=' ...') context['audiences'] = [ {'text': audience.name, 'url': '/ask-cfpb/audience-{}'.format( slugify(audience.name))} for audience in self.answer_base.audiences.all()] if self.language == 'es': tag_dict = self.Answer.valid_tags(language='es') context['tags_es'] = [tag for tag in self.answer_base.clean_tags_es if tag in tag_dict['valid_tags']] context['tweet_text'] = Truncator(self.question).chars( 100, truncate=' ...') context['disclaimer'] = get_reusable_text_snippet( SPANISH_DISCLAIMER_SNIPPET_TITLE) context['category'] = self.answer_base.category.first() elif self.language == 'en': # we're not using tags on English pages yet, so cut the overhead # tag_dict = self.Answer.valid_tags() # context['tags'] = [tag for tag in self.answer_base.clean_tags # if tag in tag_dict['valid_tags']] context['about_us'] = get_reusable_text_snippet( ABOUT_US_SNIPPET_TITLE) context['disclaimer'] = get_reusable_text_snippet( ENGLISH_DISCLAIMER_SNIPPET_TITLE) context['last_edited'] = ( self.last_edited or self.answer_base.last_edited) # breadcrumbs and/or category should reflect # the referrer if it is a consumer tools portal or # ask category page context['category'], context['breadcrumb_items'] = \ get_question_referrer_data( request, self.answer_base.category.all()) subcategories = [] for subcat in self.answer_base.subcategory.all(): if subcat.parent == context['category']: subcategories.append(subcat) for related in subcat.related_subcategories.all(): if related.parent == context['category']: subcategories.append(related) context['subcategories'] = set(subcategories) return context def get_template(self, request): printable = request.GET.get('print', False) if self.language == 'es': if printable == 'true': return 'ask-cfpb/answer-page-spanish-printable.html' return 'ask-cfpb/answer-page-spanish.html' return 'ask-cfpb/answer-page.html' def __str__(self): if self.answer_base: return '{}: {}'.format(self.answer_base.id, self.title) else: return self.title @property def status_string(self): if self.redirect_to: if not self.live: return _("redirected but not live") else: return _("redirected") else: return super(AnswerPage, self).status_string # Returns an image for the page's meta Open Graph tag @property def meta_image(self): if self.answer_base.social_sharing_image: return self.answer_base.social_sharing_image if not self.answer_base.category.exists(): return None return self.answer_base.category.first().category_image # Overrides the default of page.id for comparing against split testing # clusters. See: core.feature_flags.in_split_testing_cluster @property def split_test_id(self): return self.answer_base.id
null=True, blank=True, on_delete=models.SET_NULL, related_name='+') banner_image.help_text = "A big wide image (at least 1440x650px) to "\ "grab the viewer's attention" welcome = RichTextField(default='', blank=True) welcome.help_text = "A short introductory message" body = RichTextField(default='', blank=True) body.help_text = "An area of text for whatever you like" HomePage.content_panels = [ ImageChooserPanel('banner_image'), FieldPanel('welcome', classname="full"), InlinePanel(HomePage, 'highlights', label="Highlights"), FieldPanel('body', classname="full"), ] HomePage.promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration") ] class ContactPage(Page): class Meta: verbose_name = "ContactPage" description = "This is for our contact details." body = RichTextField(blank=True)
class BlogPage(BlogRoutes, Page): description = models.CharField( verbose_name=_('Description'), max_length=255, blank=True, help_text=_("The blog description that will appear under the title.")) header_image = models.ForeignKey(get_image_model_path(), verbose_name=_('Header image'), null=True, blank=True, on_delete=models.SET_NULL, related_name='+') display_comments = models.BooleanField(default=False, verbose_name=_('Display comments')) display_categories = models.BooleanField( default=True, verbose_name=_('Display categories')) display_tags = models.BooleanField(default=True, verbose_name=_('Display tags')) display_popular_entries = models.BooleanField( default=True, verbose_name=_('Display popular entries')) display_last_entries = models.BooleanField( default=True, verbose_name=_('Display last entries')) display_archive = models.BooleanField(default=True, verbose_name=_('Display archive')) disqus_api_secret = models.TextField(blank=True) disqus_shortname = models.CharField(max_length=128, blank=True) num_entries_page = models.IntegerField(default=5, verbose_name=_('Entries per page')) num_last_entries = models.IntegerField( default=3, verbose_name=_('Last entries limit')) num_popular_entries = models.IntegerField( default=3, verbose_name=_('Popular entries limit')) num_tags_entry_header = models.IntegerField( default=5, verbose_name=_('Tags limit entry header')) extra = BlogManager() content_panels = Page.content_panels + [ FieldPanel('description', classname="full"), ImageChooserPanel('header_image'), ] settings_panels = Page.settings_panels + [ MultiFieldPanel([ FieldPanel('display_categories'), FieldPanel('display_tags'), FieldPanel('display_popular_entries'), FieldPanel('display_last_entries'), FieldPanel('display_archive'), ], heading=_("Widgets")), MultiFieldPanel([ FieldPanel('num_entries_page'), FieldPanel('num_last_entries'), FieldPanel('num_popular_entries'), FieldPanel('num_tags_entry_header'), ], heading=_("Parameters")), MultiFieldPanel([ FieldPanel('display_comments'), FieldPanel('disqus_api_secret'), FieldPanel('disqus_shortname'), ], heading=_("Comments")), ] subpage_types = ['puput.EntryPage'] def get_entries(self): return EntryPage.objects.descendant_of(self).live().order_by( '-date').select_related('owner') def get_context(self, request, *args, **kwargs): context = super(BlogPage, self).get_context(request, *args, **kwargs) context['entries'] = self.entries context['blog_page'] = self context['search_type'] = getattr(self, 'search_type', "") context['search_term'] = getattr(self, 'search_term', "") return context @property def last_url_part(self): """ Get the BlogPage url without the domain """ return self.get_url_parts()[-1] class Meta: verbose_name = _('Blog')
class MenuItem(models.Model): allow_subnav = False link_page = models.ForeignKey( 'wagtailcore.Page', verbose_name=_('link to an internal page'), blank=True, null=True, on_delete=models.CASCADE, ) link_url = models.CharField( verbose_name=_('link to a custom URL'), max_length=255, blank=True, null=True, ) link_text = models.CharField( verbose_name=_('link text'), max_length=255, blank=True, help_text=_("Must be set if you wish to link to a custom URL."), ) handle = models.CharField( verbose_name=_('handle'), max_length=100, blank=True, help_text=_( "Use this field to optionally specify an additional value for " "each menu item, which you can then reference in custom menu " "templates.")) url_append = models.CharField( verbose_name=_("append to URL"), max_length=255, blank=True, help_text=_( "Use this to optionally append a #hash or querystring to the " "above page's URL.")) objects = MenuItemManager() class Meta: abstract = True verbose_name = _("menu item") verbose_name_plural = _("menu items") @property def menu_text(self): return self.link_text or self.link_page.title def relative_url(self, site=None): if self.link_page: return self.link_page.relative_url(site) + self.url_append return self.link_url + self.url_append def clean(self, *args, **kwargs): super(MenuItem, self).clean(*args, **kwargs) if self.link_url and not self.link_text: raise ValidationError({ 'link_text': [ _("This must be set if you're linking to a custom URL."), ] }) if not self.link_url and not self.link_page: raise ValidationError({ 'link_url': [ _("This must be set if you're not linking to a page."), ] }) if self.link_url and self.link_page: raise ValidationError( _("You cannot link to both a page and URL. Please review your " "link and clear any unwanted values.")) def __str__(self): return self.menu_text panels = ( PageChooserPanel('link_page'), FieldPanel('link_url'), FieldPanel('url_append'), FieldPanel('link_text'), FieldPanel('handle'), FieldPanel('allow_subnav'), )
class PaginaDePictos(Page): cuaderno = ParentalManyToManyField( 'Cuaderno', blank=True, help_text="Selecciona el cuaderno o cuadernos en que debe de aparecer") subtitulo = models.CharField( "SubtÃtulo de la página", max_length=254, blank=True, help_text="Texto que puede aparecer debajo del tÃtulo de la página") tit1 = models.CharField("TÃtulo de la lÃnea", max_length=254, blank=True) tit2 = models.CharField("TÃtulo de la lÃnea", max_length=254, blank=True) tit3 = models.CharField("TÃtulo de la lÃnea", max_length=254, blank=True) tit4 = models.CharField("TÃtulo de la lÃnea", max_length=254, blank=True) observaciones = RichTextField(blank=True) generarpdf = models.BooleanField("Generar pdf", default=True) ''' def serve(self, request): if "format" in request.GET: if request.GET['format'] == 'pdf': if self.generarpdf: open('{}{}.pdf'.format(ruta_pdf, self.slug), 'wb').write(generar_pdf(self)) self.generarpdf = False self.save() response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(self.slug) response.write(pdf) return response else: # Unrecognised format error message = 'No podemos exportar en este formato: ' + request.GET['format'] return HttpResponse(message, content_type='text/plain') else: # Display event page as usual return super(PaginaDePictos, self).serve(request) ''' search_fields = Page.search_fields + [ # Inherit search_fields from Page index.RelatedFields('linea1', [ index.SearchField('tit1'), index.SearchField('imagen'), index.SearchField('texto') ]) ] content_panels = Page.content_panels + [ FieldPanel('generarpdf'), FieldRowPanel([ FieldPanel('cuaderno', widget=forms.CheckboxSelectMultiple, classname="col5"), FieldPanel('subtitulo', classname="col7") ], ), MultiFieldPanel( [ FieldPanel('tit1'), InlinePanel('linea1', label="Pictos"), ], heading="Primera fila de pictos", classname="collapsible collapsed", ), MultiFieldPanel( [ FieldPanel('tit2'), InlinePanel('linea2', label="Pictos"), ], heading="Segunda fila de pictos", classname="collapsible collapsed", ), MultiFieldPanel( [ FieldPanel('tit3'), InlinePanel('linea3', label="Pictos"), ], heading="Tercera fila de pictos", classname="collapsible collapsed", ), MultiFieldPanel( [ FieldPanel('tit4'), InlinePanel('linea4', label="Pictos"), ], heading="Cuarta fila de pictos", classname="collapsible collapsed", ), FieldPanel('observaciones'), ]
class GenreFeaturePageRelationship(Orderable, models.Model): page = ParentalKey('FeatureContentPage', related_name='feature_page_genre_relationship') genre = models.ForeignKey('genre.GenreClass', related_name='genre_feature_page_relationship') panels = [FieldPanel('genre')]
class GenreClassAlbumRelationship(Orderable, models.Model): page = ParentalKey('Album', related_name='album_genre_relationship') genres = models.ForeignKey('genre.GenreClass', related_name="genre_album_relationship") panels = [FieldPanel('genres')]
except EmptyPage: items = paginator.page(paginator.num_pages) except PageNotAnInteger: items = paginator.page(1) return items class PersonIndexPage(Page, WithStreamField): subpage_types = [ 'Person', ] PersonIndexPage.content_panels = [ FieldPanel('title', classname='full title'), StreamFieldPanel('body'), ] PersonIndexPage.promote_panels = Page.promote_panels class Person(Page, WithStreamField): search_fields = Page.search_fields + [] image = models.ForeignKey( 'wagtailimages.Image', on_delete=models.PROTECT, ) subpage_types = [ 'ImagePage', ]
class CFGOVPage(Page): authors = ClusterTaggableManager(through=CFGOVAuthoredPages, blank=True, verbose_name='Authors', help_text='A comma separated list of ' + 'authors.', related_name='authored_pages') tags = ClusterTaggableManager(through=CFGOVTaggedPages, blank=True, related_name='tagged_pages') shared = models.BooleanField(default=False) language = models.CharField(choices=ref.supported_languagues, default='en', max_length=2) # This is used solely for subclassing pages we want to make at the CFPB. is_creatable = False objects = CFGOVPageManager() # These fields show up in either the sidebar or the footer of the page # depending on the page type. sidefoot = StreamField([ ('call_to_action', molecules.CallToAction()), ('related_links', molecules.RelatedLinks()), ('related_posts', organisms.RelatedPosts()), ('related_metadata', molecules.RelatedMetadata()), ('email_signup', organisms.EmailSignUp()), ('contact', organisms.MainContactInfo()), ('sidebar_contact', organisms.SidebarContactInfo()), ('rss_feed', molecules.RSSFeed()), ], blank=True) # Panels sidefoot_panels = [ StreamFieldPanel('sidefoot'), ] settings_panels = [ MultiFieldPanel(Page.promote_panels, 'Settings'), FieldPanel('tags', 'Tags'), FieldPanel('authors', 'Authors'), FieldPanel('language', 'language'), InlinePanel('categories', label="Categories", max_num=2), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), ] # Tab handler interface guide because it must be repeated for each subclass edit_handler = TabbedInterface([ ObjectList(Page.content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar/Footer'), ObjectList(settings_panels, heading='Configuration'), ]) def generate_view_more_url(self, request): tags = [] activity_log = CFGOVPage.objects.get(slug='activity-log').specific index = util.get_form_id(activity_log, request.GET) for tag in self.tags.names(): tags.append('filter%s_topics=' % index + urllib.quote_plus(tag)) tags = '&'.join(tags) return get_protected_url({'request': request}, activity_log) + '?' + tags def related_posts(self, block, hostname): from . import AbstractFilterPage related = {} queries = {} query = models.Q(('tags__name__in', self.tags.names())) search_types = [ ('blog', 'posts', 'Blog', query), ('newsroom', 'newsroom', 'Newsroom', query), ('events', 'events', 'Events', query), ] for parent_slug, search_type, search_type_name, search_query in search_types: try: parent = Page.objects.get(slug=parent_slug) search_query &= Page.objects.child_of_q(parent) if 'specific_categories' in block.value: specific_categories = ref.related_posts_category_lookup(block.value['specific_categories']) choices = [c[0] for c in ref.choices_for_page_type(parent_slug)] categories = [c for c in specific_categories if c in choices] if categories: search_query &= Q(('categories__name__in', categories)) if parent_slug == 'events': try: parent_slug = 'archive-past-events' parent = Page.objects.get(slug=parent_slug) q = (Page.objects.child_of_q(parent) & query) if 'specific_categories' in block.value: specific_categories = ref.related_posts_category_lookup(block.value['specific_categories']) choices = [c[0] for c in ref.choices_for_page_type(parent_slug)] categories = [c for c in specific_categories if c in choices] if categories: q &= Q(('categories__name__in', categories)) search_query |= q except Page.DoesNotExist: print 'archive-past-events does not exist' queries[search_type_name] = search_query except Page.DoesNotExist: print parent_slug, 'does not exist' for parent_slug, search_type, search_type_name, search_query in search_types: if 'relate_%s' % search_type in block.value \ and block.value['relate_%s' % search_type]: related[search_type_name] = \ AbstractFilterPage.objects.live_shared(hostname).filter( queries[search_type_name]).distinct().exclude( id=self.id).order_by('-date_published')[:block.value['limit']] # Return a dictionary of lists of each type when there's at least one # hit for that type. return {search_type: queryset for search_type, queryset in related.items() if queryset} def get_breadcrumbs(self, site): ancestors = self.get_ancestors() home_page_children = site.root_page.get_children() for i, ancestor in enumerate(ancestors): if ancestor in home_page_children: return ancestors[i+1:] return [] def get_appropriate_descendants(self, hostname, inclusive=True): return CFGOVPage.objects.live_shared(hostname).descendant_of(self, inclusive) def get_appropriate_siblings(self, hostname, inclusive=True): return CFGOVPage.objects.live_shared(hostname).sibling_of(self, inclusive) def get_next_appropriate_siblings(self, hostname, inclusive=False): return self.get_appropriate_siblings(hostname=hostname, inclusive=inclusive).filter(path__gte=self.path).order_by('path') def get_prev_appropriate_siblings(self, hostname, inclusive=False): return self.get_appropriate_siblings(hostname=hostname, inclusive=inclusive).filter(path__lte=self.path).order_by('-path') @property def status_string(self): if not self.live: if self.expired: return _("expired") elif self.approved_schedule: return _("scheduled") elif self.shared: return _("shared") else: return _("draft") else: if self.has_unpublished_changes: return _("live + draft") else: return _("live") def sharable_pages(self): """ Return a queryset of the pages that this user has permission to share. """ # Deal with the trivial cases first... if not self.user.is_active: return Page.objects.none() if self.user.is_superuser: return Page.objects.all() sharable_pages = Page.objects.none() for perm in self.permissions.filter(permission_type='share'): # User has share permission on any subpage of perm.page # (including perm.page itself). sharable_pages |= Page.objects.descendant_of(perm.page, inclusive=True) return sharable_pages def can_share_pages(self): """Return True if the user has permission to publish any pages""" return self.sharable_pages().exists() def route(self, request, path_components): if path_components: # Request is for a child of this page. child_slug = path_components[0] remaining_components = path_components[1:] try: subpage = self.get_children().get(slug=child_slug) except Page.DoesNotExist: raise Http404 return subpage.specific.route(request, remaining_components) else: # Request is for this very page. # If we're on the production site, make sure the version of the page # displayed is the latest version that has `live` set to True for # the live site or `shared` set to True for the staging site. staging_hostname = os.environ.get('STAGING_HOSTNAME') revisions = self.revisions.all().order_by('-created_at') for revision in revisions: page_version = json.loads(revision.content_json) if request.site.hostname != staging_hostname: if page_version['live']: return RouteResult(revision.as_page_object()) else: if page_version['shared']: return RouteResult(revision.as_page_object()) raise Http404 def permissions_for_user(self, user): """ Return a CFGOVPagePermissionTester object defining what actions the user can perform on this page """ user_perms = CFGOVUserPagePermissionsProxy(user) return user_perms.for_page(self) class Meta: app_label = 'v1' def parent(self): parent = self.get_ancestors(inclusive=False).reverse()[0].specific return parent # To be overriden if page type requires JS files every time # 'template' is used as the key for front-end consistency def add_page_js(self, js): js['template'] = [] # Retrieves the stream values on a page from it's Streamfield def _get_streamfield_blocks(self): lst = [value for key, value in vars(self).iteritems() if type(value) is StreamValue] return list(chain(*lst)) # Gets the JS from the Streamfield data def _add_streamfield_js(self, js): # Create a dictionary with keys ordered organisms, molecules, then atoms for child in self._get_streamfield_blocks(): self._add_block_js(child.block, js) # Recursively search the blocks and classes for declared Media.js def _add_block_js(self, block, js): self._assign_js(block, js) if issubclass(type(block), blocks.StructBlock): for child in block.child_blocks.values(): self._add_block_js(child, js) elif issubclass(type(block), blocks.ListBlock): self._add_block_js(block.child_block, js) # Assign the Media js to the dictionary appropriately def _assign_js(self, obj, js): try: if hasattr(obj.Media, 'js'): for key in js.keys(): if obj.__module__.endswith(key): js[key] += obj.Media.js if not [key for key in js.keys() if obj.__module__.endswith(key)]: js.update({'other': obj.Media.js}) except: pass # Returns all the JS files specific to this page and it's current Streamfield's blocks @property def media(self): js = OrderedDict() for key in ['template', 'organisms', 'molecules', 'atoms']: js.update({key: []}) self.add_page_js(js) self._add_streamfield_js(js) for key, js_files in js.iteritems(): js[key] = list(set(js_files)) return js
class BaseForm(ClusterableModel): """ A form base class, any form should inherit from this. """ name = models.CharField( max_length=255 ) slug = models.SlugField( allow_unicode=True, max_length=255, unique=True, help_text=_('Used to identify the form in template tags') ) content_type = models.ForeignKey( 'contenttypes.ContentType', verbose_name=_('content type'), related_name='streamforms', on_delete=models.SET(get_default_form_content_type) ) template_name = models.CharField( verbose_name='template', max_length=255, choices=get_setting('FORM_TEMPLATES') ) submit_button_text = models.CharField( max_length=100, default='Submit' ) store_submission = models.BooleanField( default=False ) add_recaptcha = models.BooleanField( default=False, help_text=_('Add a reCapcha field to the form.') ) success_message = models.CharField( blank=True, max_length=255, help_text=_('An optional success message to show when the form has been successfully submitted') ) error_message = models.CharField( blank=True, max_length=255, help_text=_('An optional error message to show when the form has validation errors') ) post_redirect_page = models.ForeignKey( 'wagtailcore.Page', models.SET_NULL, null=True, blank=True, related_name='+', help_text=_('The page to redirect to after a successful submission') ) settings_panels = [ FieldPanel('name', classname='full'), FieldPanel('slug'), FieldPanel('template_name'), FieldPanel('submit_button_text'), MultiFieldPanel([ FieldPanel('success_message'), FieldPanel('error_message'), ], 'Messages'), FieldPanel('store_submission'), PageChooserPanel('post_redirect_page') ] field_panels = [ InlinePanel('form_fields', label='Fields'), ] edit_handler = TabbedInterface([ ObjectList(settings_panels, heading='General'), ObjectList(field_panels, heading='Fields'), ]) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if not self.id: self.content_type = ContentType.objects.get_for_model(self) def __str__(self): return self.name class Meta: ordering = ['name', ] verbose_name = _('form') def copy(self): """ Copy this form and its fields. """ exclude_fields = ['id', 'slug'] specific_self = self.specific specific_dict = {} for field in specific_self._meta.get_fields(): # ignore explicitly excluded fields if field.name in exclude_fields: continue # pragma: no cover # ignore reverse relations if field.auto_created: continue # pragma: no cover # ignore m2m relations - they will be copied as child objects # if modelcluster supports them at all (as it does for tags) if field.many_to_many: continue # pragma: no cover # ignore parent links (baseform_ptr) if isinstance(field, models.OneToOneField) and field.rel.parent_link: continue # pragma: no cover specific_dict[field.name] = getattr(specific_self, field.name) # new instance from prepared dict values, in case the instance class implements multiple levels inheritance form_copy = self.specific_class(**specific_dict) # a dict that maps child objects to their new ids # used to remap child object ids in revisions child_object_id_map = defaultdict(dict) # create the slug - temp as will be changed from the copy form form_copy.slug = uuid.uuid4() form_copy.save() # copy child objects for child_relation in get_all_child_relations(specific_self): accessor_name = child_relation.get_accessor_name() parental_key_name = child_relation.field.attname child_objects = getattr(specific_self, accessor_name, None) if child_objects: for child_object in child_objects.all(): old_pk = child_object.pk child_object.pk = None setattr(child_object, parental_key_name, form_copy.id) child_object.save() # add mapping to new primary key (so we can apply this change to revisions) child_object_id_map[accessor_name][old_pk] = child_object.pk else: # we should never get here as there is always a FormField child class pass # pragma: no cover return form_copy copy.alters_data = True def get_data_fields(self): """ Returns a list of tuples with (field_name, field_label). """ data_fields = [ ('submit_time', _('Submission date')), ] data_fields += [ (field.clean_name, field.label) for field in self.get_form_fields() ] return data_fields def get_form(self, *args, **kwargs): form_class = self.get_form_class() form_params = self.get_form_parameters() form_params.update(kwargs) return form_class(*args, **form_params) def get_form_class(self): fb = FormBuilder(self.get_form_fields(), add_recaptcha=self.add_recaptcha) return fb.get_form_class() def get_form_fields(self): """ Form expects `form_fields` to be declared. If you want to change backwards relation name, you need to override this method. """ return self.form_fields.all() def get_form_parameters(self): return {} def get_submission_class(self): """ Returns submission class. You can override this method to provide custom submission class. Your class must be inherited from AbstractFormSubmission. """ return FormSubmission def process_form_submission(self, form): """ Accepts form instance with submitted data. Creates submission instance if self.store_submission = True. You can override this method if you want to have custom creation logic. For example, you want to additionally send an email. """ if self.store_submission: return self.get_submission_class().objects.create( form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder), form=self ) @cached_property def specific(self): """ Return this form in its most specific subclassed form. """ # the ContentType.objects manager keeps a cache, so this should potentially # avoid a database lookup over doing self.content_type. I think. content_type = ContentType.objects.get_for_id(self.content_type_id) model_class = content_type.model_class() if model_class is None: # Cannot locate a model class for this content type. This might happen # if the codebase and database are out of sync (e.g. the model exists # on a different git branch and we haven't rolled back migrations before # switching branches); if so, the best we can do is return the form # unchanged. return self # pragma: no cover elif isinstance(self, model_class): # self is already the an instance of the most specific class return self else: return content_type.get_object_for_this_type(id=self.id) @cached_property def specific_class(self): """ Return the class that this page would be if instantiated in its most specific form """ content_type = ContentType.objects.get_for_id(self.content_type_id) return content_type.model_class()
class AbstractEmailForm(BaseForm): """ A form that sends and email. You can create custom form model based on this abstract model. For example, if you need a form that will send an email. """ # do not add these fields to the email ignored_fields = ['recaptcha', 'form_id', 'form_reference'] subject = models.CharField( max_length=255 ) from_address = models.EmailField() to_addresses = MultiEmailField( help_text=_("Add one email per line") ) message = models.TextField() exclude_form_data = models.BooleanField( default=False, help_text=_("Exclude form submission data from email") ) fail_silently = models.BooleanField( default=True ) email_panels = [ FieldPanel('subject', classname="full"), FieldPanel('from_address', classname="full"), FieldPanel('to_addresses', classname="full"), FieldPanel('message', classname="full"), FieldPanel('exclude_form_data'), FieldPanel('fail_silently'), ] edit_handler = TabbedInterface([ ObjectList(BaseForm.settings_panels, heading='General'), ObjectList(BaseForm.field_panels, heading='Fields'), ObjectList(email_panels, heading='Email Submission'), ]) class Meta(BaseForm.Meta): abstract = True def process_form_submission(self, form): """ Process the form submission and send an email. """ super().process_form_submission(form) self.send_form_mail(form) def send_form_mail(self, form): """ Send an email. """ if self.exclude_form_data: from django.urls import reverse from django.contrib.sites.models import Site current_site = Site.objects.get_current().domain url = reverse('wagtailstreamforms:streamforms_submissions', args=[str(self.pk)]) content = [self.message + '\n\nYour form has a new submission.\n', ] content.append('To view the submission, go to: ' + current_site + url) else: content = [self.message + '\n\nSubmission\n', ] for name, field in form.fields.items(): data = form.cleaned_data.get(name) if name in self.ignored_fields or not data: continue # pragma: no cover label = field.label or name content.append(label + ': ' + six.text_type(data)) send_mail( self.subject, '\n'.join(content), self.from_address, self.to_addresses, self.fail_silently )
else: return content_type.get_object_for_this_type(id=self.id) @cached_property def specific_class(self): """ Return the class that this page would be if instantiated in its most specific form """ content_type = ContentType.objects.get_for_id(self.content_type_id) return content_type.model_class() if recaptcha_enabled(): # pragma: no cover BaseForm.field_panels.insert(0, FieldPanel('add_recaptcha')) class BasicForm(BaseForm): """ A basic form. """ class AbstractEmailForm(BaseForm): """ A form that sends and email. You can create custom form model based on this abstract model. For example, if you need a form that will send an email. """ # do not add these fields to the email
class FormPage(AbstractEmailForm): form_builder = HeadingFormBuilder landing_page_template = 'pages/form_page_confirmation.html' subpage_types = [] timeline_snippet = models.ForeignKey(TimelineSnippet, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') intro = RichTextField(null=True, blank=True) content = StreamField(BASE_BLOCKS + COLUMNS_BLOCKS, null=True, blank=True) confirmation_text = RichTextField( default= 'The form was submitted successfully. We will get back to you soon.') send_confirmation_email = models.BooleanField( default=False, verbose_name='Send confirmation email') confirmation_email_subject = models.CharField( max_length=500, verbose_name='Confirmation email subject', null=True, blank=True) confirmation_email_text = models.TextField( default= 'The form was submitted successfully. We will get back to you soon.', null=True, blank=True) form_title = models.CharField(max_length=500, verbose_name='Form title', null=True, blank=True) button_name = models.CharField(max_length=500, verbose_name='Button name', default='Submit') content_panels = AbstractEmailForm.content_panels + [ SnippetChooserPanel('timeline_snippet'), FieldPanel('intro'), StreamFieldPanel('content'), MultiFieldPanel([ FieldPanel('form_title'), InlinePanel('form_fields', label="Form fields"), FieldPanel('button_name'), FieldPanel('confirmation_text', classname="full"), FieldPanel('send_confirmation_email'), FieldPanel('confirmation_email_subject'), FieldPanel('confirmation_email_text', classname="full"), MultiFieldPanel([ FieldPanel('to_address', classname="full"), FieldPanel('from_address', classname="full"), FieldPanel('subject', classname="full"), ], "Email"), ], "Form Builder"), ] def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) return context def get_data_fields(self): data_fields = [ ('identifier', 'Identifier'), ] data_fields += super(FormPage, self).get_data_fields() return data_fields def get_submission_class(self): return CustomFormSubmission def serve(self, request, *args, **kwargs): context = self.get_context(request) if request.method == 'POST': form = self.get_form(request.POST, page=self, user=request.user) if form.is_valid(): self.process_form_submission(form) # render the landing_page # TODO: It is much better to redirect to it return render(request, self.get_landing_page_template(request), self.get_context(request)) else: context[ 'form_error'] = 'One of the fields below has not been filled out correctly. Please <strong>correct</strong> and <strong>resubmit</strong>.' else: form = self.get_form(page=self, user=request.user) context['form'] = form return render(request, self.get_template(request), context) def process_form_submission(self, form): # add a incremental identifier to every form submission next_identifier = CustomFormSubmission.objects.filter( page=self).order_by('-identifier').first() if next_identifier: next_identifier = next_identifier.identifier + 1 else: next_identifier = 1 self.get_submission_class().objects.create(form_data=json.dumps( form.cleaned_data, cls=DjangoJSONEncoder), page=self, identifier=next_identifier) if self.to_address: self.send_mail(form) if self.send_confirmation_email: # quick hack sending a confirmation email to the user confirmation_email_address = None # check for confirmation email address and filter headings filtered_fields = [] for field in form: if isinstance(field.field.widget, EmailInput): confirmation_email_address = field.value() elif isinstance(field.field.widget, HeadingWidget): continue filtered_fields.append(field) if confirmation_email_address: extra_content = [ '', ] for field in filtered_fields: value = field.value() if isinstance(value, list): value = ', '.join(value) extra_content.append('{}: {}'.format(field.label, value)) extra_content = '\n'.join(extra_content) send_mail( self.confirmation_email_subject, self.confirmation_email_text + extra_content, [ confirmation_email_address, ], self.from_address, )
class Album(ClusterableModel): search_fields = Page.search_fields + [ index.SearchField('title'), index.SearchField('biography'), ] title = models.CharField("The album's name", blank=True, max_length=254) slug = models.SlugField( allow_unicode=True, max_length=255, help_text= "The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/", ) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Album cover image') release_date = models.DateField() song_details = StreamField(SongStreamBlock(), verbose_name="Songs", blank=True) @property def url(self): return '/albums/' + self.slug panels = [ FieldPanel('title'), # FieldPanel('slug'), # Removed from admin view @TODO remove from DB InlinePanel('album_artist_relationship', label="Arist(s)", panels=None, min_num=1), ImageChooserPanel('image'), FieldPanel('release_date'), MultiFieldPanel([ InlinePanel('album_genre_relationship', label="Genre", panels=None, min_num=1, max_num=1) ], heading="Genres", classname="collapsible"), StreamFieldPanel('song_details'), ] # We iterate within the model over the artists, genres and subgenres # so they can be accessible to the template via a for loop def artists(self): artists = [n.artist_name for n in self.album_artist_relationship.all()] return artists def genres(self): genres = [n.genres for n in self.album_genre_relationship.all()] return genres def subgenres(self): subgenres = [ n.subgenre for n in self.album_subgenre_relationship.all() ] return subgenres def artist_name(self): artists = [ n.artist_name.title for n in self.album_artist_relationship.all() ] return ", ".join(artists) # We return the list as a string by joining all the list items. We do this # to avoid returning something like "['Fugazi']" when we want to return # "Fugazi" def __str__(self): string = ("Album: " + self.title + " by " + self.artist_name()) return string # This will return something like Album: 13 Songs by Fugazi # Need to get the result of the function call using artist_name(). Rather # than just artist_name. c/f http://stackoverflow.com/questions/31937532/python-django-query-error-cant-convert-method-object-to-str-implicitly @property def album_image(self): # fail silently if there is no profile pic or the rendition file can't # be found. Note @richbrennan worked out how to do this... try: return self.image.get_rendition('fill-400x400').img_tag() except: return '' artist_name.admin_order_field = 'album_artist_relationship__artist_name' # artist_name references the string created from the artist_name list whilst # the reverse lookup to __artist_name is the related name ForeignKey def genre(obj): genre = ','.join( [str(n.genres) for n in obj.album_genre_relationship.all()]) return genre # Again note we call `genres` because it's what we called the fk genre.admin_order_field = 'album_genre_relationship__genre' class Meta: ordering = ['title'] verbose_name = "Album" verbose_name_plural = "Albums"
class HomePageRelatedLink(Orderable, RelatedLink): page = ParentalKey('demo.HomePage', related_name='related_links') class HomePage(Page): body = StreamField(DemoStreamBlock()) search_fields = Page.search_fields + [ index.SearchField('body'), ] class Meta: verbose_name = "Homepage" HomePage.content_panels = [ FieldPanel('title', classname="full title"), StreamFieldPanel('body'), InlinePanel('carousel_items', label="Carousel items"), InlinePanel('related_links', label="Related links"), ] HomePage.promote_panels = Page.promote_panels # Standard index page class StandardIndexPageRelatedLink(Orderable, RelatedLink): page = ParentalKey('demo.StandardIndexPage', related_name='related_links') class StandardIndexPage(Page):
class TestFieldPanel(TestCase): def setUp(self): self.EventPageForm = get_form_for_model( EventPage, form_class=WagtailAdminPageForm, formsets=[]) self.event = EventPage(title='Abergavenny sheepdog trials', date_from=date(2014, 7, 20), date_to=date(2014, 7, 21)) self.EndDatePanel = FieldPanel( 'date_to', classname='full-width').bind_to_model(EventPage) def test_render_as_object(self): form = self.EventPageForm( { 'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22' }, instance=self.event) form.is_valid() field_panel = self.EndDatePanel(instance=self.event, form=form) result = field_panel.render_as_object() # check that label appears as a legend in the 'object' wrapper, # but not as a field label (that would be provided by ObjectList instead) self.assertIn('<legend>End date</legend>', result) self.assertNotIn('<label for="id_date_to">End date:</label>', result) # check that help text is not included (it's provided by ObjectList instead) self.assertNotIn('Not required if event is on a single day', result) # check that the populated form field is included self.assertIn('value="2014-07-22"', result) # there should be no errors on this field self.assertNotIn('<p class="error-message">', result) def test_render_as_field(self): form = self.EventPageForm( { 'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22' }, instance=self.event) form.is_valid() field_panel = self.EndDatePanel(instance=self.event, form=form) result = field_panel.render_as_field() # check that label is output in the 'field' style self.assertIn('<label for="id_date_to">End date:</label>', result) self.assertNotIn('<legend>End date</legend>', result) # check that help text is included self.assertIn('Not required if event is on a single day', result) # check that the populated form field is included self.assertIn('value="2014-07-22"', result) # there should be no errors on this field self.assertNotIn('<p class="error-message">', result) def test_required_fields(self): result = self.EndDatePanel.required_fields() self.assertEqual(result, ['date_to']) def test_error_message_is_rendered(self): form = self.EventPageForm( { 'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-33' }, instance=self.event) form.is_valid() field_panel = self.EndDatePanel(instance=self.event, form=form) result = field_panel.render_as_field() self.assertIn('<p class="error-message">', result) self.assertIn('<span>Enter a valid date.</span>', result)
class FeatureContentPage(Page): """ This is a feature content page for all of your interviews, news etc. """ # TODO This almost entirely duplicates StandardPage class. They should be # referencing something to reduce the duplication search_fields = Page.search_fields + [ # Defining what fields the search catches index.SearchField('introduction'), index.SearchField('body'), ] date = models.DateField("Post date", help_text='blah') image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Image to be used where this feature content is listed') # Page models mostly use generic Django fields. Here we're using a choice # field for how the images should be styled. # https://docs.djangoproject.com/en/1.10/ref/models/fields/#choices # You can reference core/blocks.py for how you can reference choices within # StreamField image_choices_list = ( ('fit', 'Contained width'), ('expand', 'Expanded width'), ('full', 'Full width'), ('hide', 'Hidden (e.g. only display on listings)'), ) image_choices = models.CharField( max_length=255, choices=image_choices_list, help_text='How you want the image to be displayed on the feature page') # Note below that standard blocks use 'help_text' for supplementary text # rather than 'label' as with StreamField introduction = models.TextField( blank=True, help_text="Text to show at the top of the individual page") # Using CharField for little reason other than showing a different input # type Wagtail allows you to use any field type Django follows, so you can # use anything from # https://docs.djangoproject.com/en/1.9/ref/models/fields/#field-types listing_introduction = models.CharField( max_length=250, blank=True, help_text= "Text shown on listing pages, if empty will show 'Introduction' field content" ) # Note below we're calling StreamField from another location. The # `StandardBlock` class is a shared asset across the site. It is defined in # core > blocks.py. It is just as 'correct' to define the StreamField # directly within the model, but this method aids consistency. body = StreamField(StandardBlock(), help_text="Blah blah blah", blank=True) content_panels = Page.content_panels + [ # The content panels are displaying the components of content we defined # in the StandardPage class above. If you add something to the class and # want it to appear for the editor you have to define it here too # A full list of the panel types you can use is at # http://docs.wagtail.io/en/latest/reference/pages/panels.html # If you add a different type of panel ensure you've imported it from # wagtail.wagtailadmin.edit_handlers in the `From` statements at the top # of the model InlinePanel('artist_groups', label="Artist(s)"), # SnippetChooserPanel('artist'), FieldPanel('date'), MultiFieldPanel([ FieldPanel('introduction'), FieldPanel('listing_introduction'), ], heading="Introduction", classname="collapsible"), MultiFieldPanel([ ImageChooserPanel('image'), FieldPanel('image_choices'), ], heading="Image details", classname="collapsible"), StreamFieldPanel('body'), MultiFieldPanel( # This duplicates the album/models.py album classa. [ InlinePanel('feature_page_genre_relationship', label="Genre", panels=None, min_num=1, max_num=3), ], heading="Genres", classname="collapsible"), InlinePanel('feature_page_artist_relationship', label="Artists"), InlinePanel('feature_page_author_relationship', label="Authors", help_text='something'), ] @property def features_index(self): # I'm not convinced this is altogether necessary... but still we're # going from feature_content_page -> feature_index_page return self.get_ancestors().type(FeatureIndexPage).last() parent_page_types = [ 'feature_content_page.FeatureIndexPage' # app.model ] subpage_types = [] # We're returning artists and authors to allow the template to grab the # related content. Note the fact we use the related name # `artist_feature_page_relationship` to grab them. In the template we'll use # a loop to grab them e.g. {% for artist in page.artists %} # # You don't need to place this at the end of the model, but conventionally # it makes sense to put it here def artists(self): artists = [ n.artist for n in self.feature_page_artist_relationship.all() ] return artists def authors(self): authors = [ n.author for n in self.feature_page_author_relationship.all() ] return authors def genres(self): genres = [n.genre for n in self.feature_page_genre_relationship.all()] return genres def subgenres(self): subgenres = [ n.subgenre for n in self.feature_page_subgenre_relationship.all() ] return subgenres
class BlogPage(Page): header_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) intro = models.CharField(max_length=250) # body = RichTextField(blank=True) body = StreamField([ ('h1', CharBlock(icon="title", classanme="title")), ('h2', CharBlock(icon="title", classanme="title")), ('h3', CharBlock(icon="title", classanme="title")), ('h4', CharBlock(icon="title", classanme="title")), ('h5', CharBlock(icon="title", classanme="title")), ('h6', CharBlock(icon="title", classanme="title")), # ('intro', RichTextBlock(icon="pilcrow")), ('paragraph', RichTextBlock(icon="pilcrow")), ('aligned_image', ImageBlock(label="Aligned image", icon="image")), ('pullquote', PullQuoteBlock()), ('raw_html', RawHTMLBlock(label='Raw HTML', icon="code")), ('embed', EmbedBlock(icon="code")), ]) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) date = models.DateField("Post date") # categories = ParentalManyToManyField('blog.BlogCategory', blank=True) feed_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) # def main_image(self): # gallery_item = self.gallery_images.first() # if gallery_item: # return gallery_item.image # else: # return None @property def has_authors(self): for author in self.related_author.all(): if author.author: return True search_field = Page.search_fields + [ index.SearchField('intro'), index.SearchField('body'), ] content_panels = Page.content_panels + [ # MultiFieldPanel([ # FieldPanel('date'), # FieldPanel('tags'), # FieldPanel('categories', widget=forms.CheckboxSelectMultiple), # ], heading="Blog information"), # FieldPanel('date'), ImageChooserPanel('header_image'), FieldPanel('date'), FieldPanel('intro'), StreamFieldPanel('body'), InlinePanel('related_author', label="Author"), # FieldPanel('tags'), ImageChooserPanel('feed_image'), # InlinePanel('gallery_images', label='Gallery images') ]
class FlatMenu(MenuWithMenuItems): site = models.ForeignKey('wagtailcore.Site', verbose_name=_('site'), related_name="flat_menus", db_index=True, on_delete=models.CASCADE) title = models.CharField(verbose_name=_('title'), max_length=255, help_text=_("For internal reference only.")) handle = models.SlugField( verbose_name=_('handle'), max_length=100, help_text=_( "Used to reference this menu in templates etc. Must be unique " "for the selected site.")) heading = models.CharField( verbose_name=_('heading'), max_length=255, blank=True, help_text=_("If supplied, appears above the menu when rendered.")) max_levels = models.PositiveSmallIntegerField( verbose_name=_('maximum levels'), choices=app_settings.MAX_LEVELS_CHOICES, default=1, help_text=mark_safe( _("The maximum number of levels to display when rendering this " "menu. The value can be overidden by supplying a different " "<code>max_levels</code> value to the <code>{% flat_menu %}" "</code> tag in your templates."))) use_specific = models.PositiveSmallIntegerField( verbose_name=_('specific page usage'), choices=app_settings.USE_SPECIFIC_CHOICES, default=app_settings.USE_SPECIFIC_AUTO, help_text=mark_safe( _("Controls how 'specific' pages objects are fetched and used when " "rendering this menu. This value can be overidden by supplying a " "different <code>use_specific</code> value to the <code>" "{% flat_menu %}</code> tag in your templates."))) base_form_class = FlatMenuAdminForm class Meta: unique_together = ("site", "handle") verbose_name = _("flat menu") verbose_name_plural = _("flat menus") @classmethod def get_for_site(cls, handle, site, fall_back_to_default_site_menus=False): """ Get a FlatMenu instance with a matching `handle` for the `site` provided - or for the 'default' site if not found. """ menu = cls.objects.filter(handle__exact=handle, site=site).first() if (menu is None and fall_back_to_default_site_menus and not site.is_default_site): return cls.objects.filter(handle__exact=handle, site__is_default_site=True).first() return menu def __str__(self): return '%s (%s)' % (self.title, self.handle) def clean(self, *args, **kwargs): # Raise validation error for unique_together constraint, as it's not # currently handled properly by wagtail clashes = FlatMenu.objects.filter(site=self.site, handle=self.handle) if self.pk: clashes = clashes.exclude(pk__exact=self.pk) if clashes.count(): msg = _("Site and handle must create a unique combination. A menu " "already exists with these same two values.") raise ValidationError({ 'site': [msg], 'handle': [msg], }) super(FlatMenu, self).clean(*args, **kwargs) panels = ( MultiFieldPanel(heading=_("Settings"), children=( FieldPanel('title'), FieldPanel('site'), FieldPanel('handle'), FieldPanel('heading'), )), InlinePanel('menu_items', label=_("menu items")), MultiFieldPanel( heading=_("Advanced settings"), children=(FieldPanel('max_levels'), FieldPanel('use_specific')), classname="collapsible collapsed", ), )
def get_context(self, request, *args, **kwargs): context = super(ProductPage, self).get_context(request, *args, **kwargs) context['products'] = self.get_product_index( ).productindexpage.products context = get_product_context(context) return context class Meta: verbose_name = _('Product') verbose_name_plural = _('Products') parent_page_types = ['fundraiser.ProductIndexPage'] ProductPage.content_panels = [ FieldPanel('title', classname="full title"), MultiFieldPanel([ FieldPanel('tags'), InlinePanel('categories', label=_("Categories")), ], heading="Tags and Categories"), ImageChooserPanel('header_image'), FieldPanel('teaser', classname="full intro"), FieldPanel('description', classname="full"), FieldPanel('organisation', classname="full organisation"), FieldPanel('prize', classname="full prize"), FieldPanel('stock', classname="full stock"), ]
class CategoryEntryPage(models.Model): category = models.ForeignKey(Category, related_name="+", verbose_name=_('Category')) page = ParentalKey('EntryPage', related_name='entry_categories') panels = [FieldPanel('category')]
class ProductPage(RoutablePageMixin, Page): teaser = models.TextField() description = RichTextField(blank=True) organisation = models.CharField(max_length=250, blank=True) stock = models.PositiveIntegerField() prize = models.PositiveIntegerField() tags = ClusterTaggableManager(through=ProductPageTag, blank=True) date = models.DateField( _("Post date"), default=datetime.datetime.today, help_text=_("This date may be displayed on the blog post. It is not " "used to schedule posts to go live at a later date.")) header_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_('Header image')) author = models.ForeignKey( settings.AUTH_USER_MODEL, blank=True, null=True, limit_choices_to=product_limit_author_choices, verbose_name=_('Author'), on_delete=models.SET_NULL, related_name='author_product_pages', ) search_fields = Page.search_fields + ( index.SearchField('description'), index.SearchField('organisation'), ) settings_panels = [ MultiFieldPanel([ FieldRowPanel([ FieldPanel('go_live_at'), FieldPanel('expire_at'), ], classname="label-above"), ], 'Scheduled publishing', classname="publishing"), FieldPanel('date'), FieldPanel('author'), ] def save_revision(self, *args, **kwargs): if not self.author: self.author = self.owner return super(ProductPage, self).save_revision(*args, **kwargs) def get_absolute_url(self): return self.url def get_product_index(self): # Find closest ancestor which is a product index return self.get_ancestors().type(ProductIndexPage).last() def get_context(self, request, *args, **kwargs): context = super(ProductPage, self).get_context(request, *args, **kwargs) context['products'] = self.get_product_index( ).productindexpage.products context = get_product_context(context) return context class Meta: verbose_name = _('Product') verbose_name_plural = _('Products') parent_page_types = ['fundraiser.ProductIndexPage']
class AbstractFobiFormPage(Page): """An abstract Fobi form page. Pages implementing a Fobi form should inherit from it. :property fobi.models.FormEntry form_entry: Form entry to be rendered. """ form_entry = models.ForeignKey('fobi.FormEntry', verbose_name=_("Form"), on_delete=models.PROTECT) form_template_name = models.CharField( _("Form template name"), max_length=255, null=True, blank=True, choices=get_form_template_choices(), help_text=_( "Choose an alternative template to render the form with. Leave " "blank to use the default for this page type (e.g. " "fobi_form_page.html).")) hide_form_title = models.BooleanField( _("Hide form title"), default=False, help_text=_("If checked, no form title is shown.")) form_title = models.CharField( _("Form title"), max_length=255, null=True, blank=True, help_text=_("Overrides the default form title.")) form_submit_button_text = models.CharField( _("Submit button text"), max_length=255, null=True, blank=True, help_text=_("Overrides the default form submit button text.")) success_page_template_name = models.CharField( _("Success page template name"), max_length=255, null=True, blank=True, choices=get_success_page_template_choices(), help_text=_( "Choose an alternative template to render the success page with. " "Leave blank to use the default for this page type (e.g. " "fobi_form_page_success.html).")) hide_success_page_title = models.BooleanField( _("Hide success page title"), default=False, help_text=_("If checked, no success page title is shown.")) success_page_title = models.CharField( _("Success page title"), max_length=255, null=True, blank=True, help_text=_("Overrides the default success page title.")) success_page_text = models.TextField( _("Success page text"), null=True, blank=True, help_text=_("Overrides the default success page text.")) form_page_panels = [ FieldPanel('hide_form_title'), FieldPanel('form_title'), FieldPanel('form_entry'), FieldPanel('form_submit_button_text'), ] if get_form_template_choices(): form_page_panels.append(FieldPanel('form_template_name')) success_page_panels = [ FieldPanel('hide_success_page_title'), FieldPanel('success_page_title'), FieldPanel('success_page_text'), ] if get_success_page_template_choices(): success_page_panels.append(FieldPanel('success_page_template_name')) content_panels = Page.content_panels + [ MultiFieldPanel(form_page_panels, heading=_('Form page')), MultiFieldPanel(success_page_panels, heading=_('Success page')), ] preview_modes = [ ('form', _('Form page')), ('success', _('Success page')), ] class Meta(object): """Meta class.""" verbose_name = _('Fobi form page') verbose_name_plural = _('Fobi form pages') abstract = True def __init__(self, *args, **kwargs): super(AbstractFobiFormPage, self).__init__(*args, **kwargs) # Some wagtail magic... if not hasattr(self, 'form_template'): name, ext = os.path.splitext(self.template) self.form_template = name + '_form' + ext if not hasattr(self, 'success_template'): name, ext = os.path.splitext(self.template) self.success_template = name + '_form_success' + ext def get_form_template(self, request): """Get an alternative template name. Get an alternative template name from the object's ``form_template_name`` field, or the ``form_template`` attr defined on the page type model. :param django.http.HttpRequest request: """ return self.form_template_name or self.form_template def get_success_template(self, request): """Get an alternative template name. Get an alternative template name from the object's ``success_page_template_name`` field, or the ``success_template`` attr defined on the page type model. :param django.http.HttpRequest request: """ return self.success_page_template_name or self.success_template def serve(self, request, *args, **kwargs): """Serve the page using the ``FobiFormProcessor``.""" fobi_form_processor = FobiFormProcessor() response = fobi_form_processor.process(request, instance=self) if response: return response # TODO: Returning HttpResponse seems dirty. See if it can be # replaced with TemplateResponse. return HttpResponse(fobi_form_processor.rendered_output) def serve_preview(self, request, mode): """Serve the page in Wagtail's 'preview' mode.""" if mode == 'success': fobi_form_processor = FobiFormProcessor() # TODO: Returning HttpResponse seems dirty. See if it can be # replaced with TemplateResponse. return HttpResponse( fobi_form_processor.show_thanks_page(request, self)) else: return super(AbstractFobiFormPage, self).serve_preview(request, mode)
def get_context(self, request): # Get list of live Gallery pages that are descendants of this page pages = GalleryPage.objects.live().descendant_of(self) # Update template context context = super(GalleryIndexPage, self).get_context(request) context['pages'] = pages return context class Meta: verbose_name = "Gallery Index Page" GalleryIndexPage.content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('intro', classname="full") ] GalleryIndexPage.promote_panels = [ MultiFieldPanel(Page.promote_panels, "SEO and metadata fields"), ImageChooserPanel('feed_image'), ] class GalleryPageTag(TaggedItemBase): content_object = ParentalKey('photo_gallery.GalleryPage', related_name='tagged_items') class GalleryPage(Page):
class AnswerCategoryPage(RoutablePageMixin, SecondaryNavigationJSMixin, CFGOVPage): """ A routable page type for Ask CFPB category pages and their subcategories. """ from ask_cfpb.models import Answer, Audience, Category, SubCategory objects = CFGOVPageManager() content = StreamField([], null=True) ask_category = models.ForeignKey( Category, blank=True, null=True, on_delete=models.PROTECT, related_name='category_page') ask_subcategory = models.ForeignKey( SubCategory, blank=True, null=True, on_delete=models.PROTECT, related_name='subcategory_page') content_panels = CFGOVPage.content_panels + [ FieldPanel('ask_category', Category), StreamFieldPanel('content'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) def get_template(self, request): if self.language == 'es': return 'ask-cfpb/category-page-spanish.html' return 'ask-cfpb/category-page.html' def get_context(self, request, *args, **kwargs): context = super( AnswerCategoryPage, self).get_context(request, *args, **kwargs) sqs = SearchQuerySet() if self.language == 'es': sqs = sqs.filter(content=self.ask_category.name_es) else: sqs = sqs.filter(content=self.ask_category.name) sqs = sqs.models(self.Category) try: facet_map = sqs[0].facet_map except IndexError: facet_map = self.ask_category.facet_map facet_dict = json.loads(facet_map) subcat_ids = facet_dict['subcategories'].keys() answer_ids = facet_dict['answers'].keys() audience_ids = facet_dict['audiences'].keys() subcats = self.SubCategory.objects.filter( pk__in=subcat_ids).values( 'id', 'slug', 'slug_es', 'name', 'name_es') answers = self.Answer.objects.filter( pk__in=answer_ids).order_by('-pk').values( 'id', 'question', 'question_es', 'slug', 'slug_es', 'answer_es') for a in answers: a['answer_es'] = Truncator(a['answer_es']).words( 40, truncate=' ...') audiences = self.Audience.objects.filter( pk__in=audience_ids).values('id', 'name') context.update({ 'answers': answers, 'audiences': audiences, 'facets': facet_dict, 'choices': subcats, 'results_count': answers.count(), 'get_secondary_nav_items': get_ask_nav_items }) if self.language == 'en': context['about_us'] = get_reusable_text_snippet( ABOUT_US_SNIPPET_TITLE) context['disclaimer'] = get_reusable_text_snippet( ENGLISH_DISCLAIMER_SNIPPET_TITLE) context['breadcrumb_items'] = get_ask_breadcrumbs() elif self.language == 'es': context['tags'] = self.ask_category.top_tags_es return context # Returns an image for the page's meta Open Graph tag @property def meta_image(self): return self.ask_category.category_image @route(r'^$') def category_page(self, request): context = self.get_context(request) paginator = Paginator(context.get('answers'), 20) page_number = validate_page_number(request, paginator) page = paginator.page(page_number) context.update({ 'paginator': paginator, 'current_page': page_number, 'questions': page, }) return TemplateResponse( request, self.get_template(request), context) @route(r'^(?P<subcat>[^/]+)/$') def subcategory_page(self, request, **kwargs): subcat = self.SubCategory.objects.filter( slug=kwargs.get('subcat')).first() if subcat: self.ask_subcategory = subcat else: raise Http404 context = self.get_context(request) id_key = str(subcat.pk) answers = context['answers'].filter( pk__in=context['facets']['subcategories'][id_key]) paginator = Paginator(answers, 20) page_number = validate_page_number(request, paginator) page = paginator.page(page_number) context.update({ 'paginator': paginator, 'current_page': page_number, 'results_count': answers.count(), 'questions': page, 'breadcrumb_items': get_ask_breadcrumbs( self.ask_category) }) return TemplateResponse( request, self.get_template(request), context)
menu_name = models.CharField(max_length=255, null=False, blank=False) @property def menu_items_template(self): menu_items_template = self.menu_items.all() return menu_items_template def __str__(self): # __unicode__ on Python 2 return self.menu_name class Meta: verbose_name = "Navigation menu" Menu.panels = [ FieldPanel('menu_name', classname='full title'), InlinePanel('menu_items', label="Menu Items", min_num=1, help_text='Set the menu items for the current menu.') ] # Related pages class RelatedPage(Orderable, models.Model): page = models.ForeignKey('wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+')
class FormPage(AbstractEmailForm): intro = RichTextField(blank=True) side_panel_title = models.CharField(max_length=255, blank=True) side_panel_content = RichTextField(blank=True) success_message = models.CharField(max_length=255) thank_you_page = models.ForeignKey('formbuilder.FormThankYouPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') subpage_types = ['formbuilder.FormThankYouPage'] # Override send_mail method to add 'reply-to' header, timestamp subject def send_mail(self, form): addresses = [x.strip() for x in self.to_address.split(',')] content = [] reply_to = ([form.data['email']] if 'email' in form.data else None) # Add timestamp to subject, to avoid gmail-style conversation views time = str(datetime.datetime.now()).split('.')[0] subject = '{} [{}]'.format(self.subject, time) for field in form: value = field.value() if isinstance(value, list): value = ', '.join(value) content.append('{}: {}'.format(field.label, value)) content = '\n'.join(content) connection = get_connection( username=settings.EMAIL_HOST_USER_INTERNAL, password=settings.EMAIL_HOST_PASSWORD_INTERNAL) email = EmailMessage(subject, content, self.from_address, addresses, connection=connection, reply_to=reply_to) email.send(fail_silently=False) # Override serve method to enable ajax def serve(self, request): if request.method == 'POST': # honeypot if len(request.POST.get('url_h', '')): messages.success(request, self.success_message) return HttpResponseRedirect(self.url) form = self.get_form(request.POST) if form.is_valid(): self.process_form_submission(form) messages.success(request, self.success_message) if request.is_ajax(): # Valid ajax post data = {'messages': []} for message in messages.get_messages(request): data['messages'].append({ "level": message.level, "level_tag": message.level_tag, "message": message.message, }) data['messages_html'] = render_to_string( 'includes/messages.html', {'messages': messages.get_messages(request)}) return JsonResponse(data) else: # Valid (non-ajax) post if self.thank_you_page: return HttpResponseRedirect(self.thank_you_page.url) else: return HttpResponseRedirect(self.url) elif request.is_ajax(): # Invalid ajax post data = {'errors': form.errors} return JsonResponse(data, status=400) else: # GET request form = self.get_form() context = self.get_context(request) context['form'] = form return render(request, self.template, context) content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('intro', classname="full"), MultiFieldPanel([ FieldPanel('side_panel_title'), FieldPanel('side_panel_content', classname="full"), ], "Side Panel"), InlinePanel('form_fields', label="Form fields"), FieldPanel('success_message', classname="full"), PageChooserPanel('thank_you_page'), MultiFieldPanel([ FieldPanel('to_address', classname="full"), FieldPanel('from_address', classname="full"), FieldPanel('subject', classname="full"), ], "Email") ]
class HomePage(Page): body = RichTextField(blank=True) content_panels = Page.content_panels + [ FieldPanel('body', classname="full") ]
def setUp(self): fake_field = self.FakeField() fake_field.field = self.FakeClass() self.field_panel = FieldPanel('barbecue', 'snowman')( instance=True, form={'barbecue': fake_field})
class FeatureIndexPage(Page): listing_introduction = models.TextField( help_text= 'Text to describe this section. Will appear on other pages that reference this feature section', blank=True) introduction = models.TextField( help_text='Text to describe this section. Will appear on the page', blank=True) body = StreamField( StandardBlock(), blank=True, help_text="No good reason to have this here, but in case there's a" "feature section I can't think of") search_fields = Page.search_fields + [ index.SearchField('listing_introduction'), index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('listing_introduction'), FieldPanel('introduction'), StreamFieldPanel('body') ] parent_page_types = ['home.HomePage'] # Defining what content type can sit under the parent subpage_types = ['FeatureContentPage'] # @property # def features(self): # """ # Return feature pages that will live under this index page. # This is now redundant since we return our features via # the `get_filtered_feature_pages` function below. # """ # return FeatureContentPage.objects.live().descendant_of(self).order_by('-date') def filter_years(self): """ Return a collection of years from the date field of feature pages beneath this page. """ years = set() features = FeatureContentPage.objects.live().descendant_of(self) year_dates = features.dates('date', 'year', order='DESC') for date in year_dates: years.add(date.year) return sorted(years, reverse=True) def genres(self): """ Return a list of genres from pages that have a relationship defined with a genre and are living beneath this page. """ genres = set() for feature_content_page in FeatureContentPage.objects.live( ).descendant_of(self): feature_genres = [ d.genre for d in feature_content_page.feature_page_genre_relationship.all() ] for genre in feature_genres: genres.add( FilterObject(id=genre.id, name=genre.title, slug=genre.slug)) return sorted(genres, key=lambda d: d.name) def paginate(self, request, objects): page = request.GET.get('page') paginator = Paginator(objects, settings.DEFAULT_PER_PAGE) try: pages = paginator.page(page) except PageNotAnInteger: pages = paginator.page(1) except EmptyPage: pages = paginator.page(paginator.num_pages) return pages def get_filtered_feature_pages(self, request={}): """ Return a filtered queryset of live feature pages that are decendants of this page. """ features = FeatureContentPage.objects.live().descendant_of(self) # The first step is to create a `features` variable that we populate # with a query. This will return all feature_content_pages that are # live (e.g. not draft) and a descendant of this index page is_filtering = False # Second is to create a filter variable. By default it is set to false request_filters = {} for k, v in request.GET.items(): request_filters[k] = (v) # Here the `=(v)` will accept any value and will trigger `is_filtering` # to be True in year and genre below if populated # filter by year year = request_filters.get('year', '') if year: is_filtering = True features = features.filter(date__year=year) # This appends the `features` variable with a filter. The filter in this # case being a the 'year', which we access via the double underscore # ('__') reverse lookup from the 'date' field. # filter by genre genre = request_filters.get('genre', '') if genre: is_filtering = True features = features.filter( feature_page_genre_relationship__genre__slug=genre) # We filter on genre__slug so that we can guarantee a response # that doesn't include spaces e.g. 'heavy-metal' rather than 'heavy # metal' but it gives more useful information than genre__id sort_by = request_filters.get('sort_by', 'modified') if sort_by == 'date-asc': features = features.order_by('first_published_at') if sort_by == 'date-desc': features = features.order_by('-first_published_at') if not is_filtering: pass filters = {'year': year, 'genre': genre, 'sort_by': sort_by} return features, filters, is_filtering def get_context(self, request): """ Overriding the context to get more control over what we return. See the section `SEPARATED CONTEXT & PAGINATION` at the end of this .py file for details on how it works. """ context = super(FeatureIndexPage, self).get_context(request) # filters features, filters, is_filtering = self.get_filtered_feature_pages( request) # Pagination features = self.paginate(request, features) # (request, features) looks for the 'features' on line 392 context['features'] = features context['filters'] = filters context['is_filtering'] = is_filtering return context # We use this property to allow the homepage to get the children of the # referenced index pages @property def children(self): return self.get_children().specific().live()
class TestFieldPanel(TestCase): def setUp(self): self.EventPageForm = get_form_for_model(EventPage, formsets=[]) self.event = EventPage(title='Abergavenny sheepdog trials', date_from=date(2014, 7, 20), date_to=date(2014, 7, 21)) self.EndDatePanel = FieldPanel('date_to', classname='full-width').bind_to_model(EventPage) def test_render_as_object(self): form = self.EventPageForm( {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22'}, instance=self.event) form.is_valid() field_panel = self.EndDatePanel( instance=self.event, form=form ) result = field_panel.render_as_object() # check that label appears in the 'object' wrapper as well as the field self.assertIn('<legend>End date</legend>', result) self.assertIn('<label for="id_date_to">End date:</label>', result) # check that help text is included self.assertIn('Not required if event is on a single day', result) # check that the populated form field is included self.assertIn('value="2014-07-22"', result) # there should be no errors on this field self.assertNotIn('<p class="error-message">', result) def test_render_as_field(self): form = self.EventPageForm( {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22'}, instance=self.event) form.is_valid() field_panel = self.EndDatePanel( instance=self.event, form=form ) result = field_panel.render_as_field() # check that label is output in the 'field' style self.assertIn('<label for="id_date_to">End date:</label>', result) self.assertNotIn('<legend>End date</legend>', result) # check that help text is included self.assertIn('Not required if event is on a single day', result) # check that the populated form field is included self.assertIn('value="2014-07-22"', result) # there should be no errors on this field self.assertNotIn('<p class="error-message">', result) def test_required_fields(self): result = self.EndDatePanel.required_fields() self.assertEqual(result, ['date_to']) def test_error_message_is_rendered(self): form = self.EventPageForm( {'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-33'}, instance=self.event) form.is_valid() field_panel = self.EndDatePanel( instance=self.event, form=form ) result = field_panel.render_as_field() self.assertIn('<p class="error-message">', result) self.assertIn('<span>Enter a valid date.</span>', result)
class ResearchSummariesIndexPage(Page, SocialFields, ListingFields): introduction = RichTextField(blank=True) content_panels = Page.content_panels + [ FieldPanel('introduction'), ] promote_panels = (Page.promote_panels + SocialFields.promote_panels + ListingFields.promote_panels) subpage_types = ['research_summaries.ResearchSummaryPage'] @classmethod def can_create_at(cls, parent): """Do not allow to create more than one instance of this page""" return super().can_create_at(parent) and not cls.objects.count() @cached_property def _children_research_summary(self): return ResearchSummaryPage.objects.live().public().descendant_of(self) @cached_property def updated_at(self): latest_page = self._children_research_summary.order_by( '-updated_at').first() latest_date = None if latest_page: latest_date = latest_page.updated_at return latest_date def get_context(self, request, *args, **kwargs): date_from = request.GET.get('date_from', None) search_date_from = None date_to = request.GET.get('date_to', None) search_date_to = None search_research_type = request.GET.get('research_type', None) search_rec_opinion = request.GET.get('rec_opinion', None) search_query = request.GET.get('query', None) page_number = request.GET.get('page', 1) search_results = self._children_research_summary # Convert dates to the Python format if date_from: try: # Can return None or raise ValueError in case of bad format search_date_from = parse_date(date_from, dayfirst=True).date() except ValueError: search_date_from = None if date_to: try: # Can return None or raise ValueError in case of bad format search_date_to = parse_date(date_to, dayfirst=True).date() except ValueError: search_date_to = None # Swap dates around if "date from" happens after "date to". if search_date_from and search_date_to: if search_date_from > search_date_to: search_date_to, search_date_from = search_date_from, search_date_to # Search queryset if search_date_to: search_results = search_results.filter( date_of_rec_opinion__lte=search_date_to) if search_date_from: search_results = search_results.filter( date_of_rec_opinion__gte=search_date_from) # Research types to be displayed as is non_aliased_research_types = dict( ResearchType.objects.filter( harp_study_type_id__in=ResearchType.NON_ALIASED_STUDY_TYPE_IDS ).values_list('pk', 'name')) # Add the alias for rest research types alias_fake_id = 0 display_research_types = non_aliased_research_types.copy() display_research_types.update({ alias_fake_id: ResearchType.ALIAS_NAME, }) try: search_research_type = int(search_research_type) if search_research_type == alias_fake_id: search_results = search_results.exclude( research_type__in=non_aliased_research_types.keys()) elif search_research_type in non_aliased_research_types.keys(): search_results = search_results.filter( research_type=search_research_type) except (TypeError, ValueError): pass # rec opinion filter if search_rec_opinion: search_results = search_results.filter( rec_opinion=search_rec_opinion) if search_query: search_results = search_results.search(search_query, operator='and') # Pagination paginator = Paginator(search_results, settings.DEFAULT_PER_PAGE) try: search_results = paginator.page(page_number) except PageNotAnInteger: search_results = paginator.page(1) except EmptyPage: search_results = paginator.page(paginator.num_pages) extra_url_params = { 'date_from': date_from, 'date_to': date_to, 'research_type': search_research_type, 'rec_opinion': search_rec_opinion, } context = super().get_context(request, *args, **kwargs) context.update({ 'search_research_type': search_research_type, 'search_rec_opinion': search_rec_opinion, 'search_query': search_query, 'search_results': search_results, 'display_research_types': display_research_types, 'search_date_from': search_date_from, 'search_date_to': search_date_to, 'rec_opinions': REC_OPINION_CHOICES, 'extra_url_params': urlencode(extra_url_params), }) context.update(get_adjacent_pages(paginator, search_results.number)) return context