class PortfolioPageJobCareer(GraphQLEnabledModel, Orderable): portfolio = ParentalKey(PortfolioPage, on_delete=models.CASCADE, related_name='job_career', verbose_name=u'職務経歴') title = models.CharField(verbose_name=u'タイトル', max_length=50) start_date = models.DateField(u'開始日', auto_now=False, auto_now_add=False) end_date = models.DateField(u'終了日', auto_now=False, auto_now_add=False, blank=True, null=True) job_role = models.CharField(u'役割', max_length=50, blank=True, null=True) description = MarkdownField(verbose_name=u'説明', blank=True, null=True) panels = [ FieldPanel('title'), FieldPanel('start_date'), FieldPanel('end_date'), FieldPanel('job_role'), MarkdownPanel('description') ] graphql_fields = [ GraphQLField('title'), GraphQLField('start_date'), GraphQLField('end_date'), GraphQLField('job_role'), GraphQLField('description'), ]
class StandardPage(GraphQLEnabledModel, Page): """ A generic content page. On this demo site we use it for an about page but it could be used for any type of page content that only needs a title, image, introduction and body field """ introduction = models.TextField(help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text= 'Landscape mode only; horizontal width between 1000px and 3000px.') body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) content_panels = Page.content_panels + [ FieldPanel('introduction', classname="full"), StreamFieldPanel('body'), ImageChooserPanel('image'), ] graphql_fields = [ GraphQLField('introduction'), GraphQLField('image'), GraphQLField('body'), ]
class People(GraphQLEnabledModel, index.Indexed, ClusterableModel): """ A Django model to store People objects. It uses the `@register_snippet` decorator to allow it to be accessible via the Snippets UI (e.g. /admin/snippets/base/people/) `People` uses the `ClusterableModel`, which allows the relationship with another model to be stored locally to the 'parent' model (e.g. a PageModel) until the parent is explicitly saved. This allows the editor to use the 'Preview' button, to preview the content, without saving the relationships to the database. https://github.com/wagtail/django-modelcluster """ first_name = models.CharField("First name", max_length=254) last_name = models.CharField("Last name", max_length=254) job_title = models.CharField("Job title", max_length=254) image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') panels = [ MultiFieldPanel([ FieldRowPanel([ FieldPanel('first_name', classname="col6"), FieldPanel('last_name', classname="col6"), ]) ], "Name"), FieldPanel('job_title'), ImageChooserPanel('image') ] search_fields = [ index.SearchField('first_name'), index.SearchField('last_name'), ] graphql_fields = [ GraphQLField('first_name'), GraphQLField('last_name'), GraphQLField('job_title'), GraphQLField('image'), ] @property def thumb_image(self): # Returns an empty string if there is no profile pic or the rendition # file can't be found. try: return self.image.get_rendition('fill-50x50').img_tag() except: return '' def __str__(self): return '{} {}'.format(self.first_name, self.last_name) class Meta: verbose_name = 'Person' verbose_name_plural = 'People'
class ArticlePageRelatedLink(GraphQLEnabledModel, Orderable): page = ParentalKey(ArticlePage, on_delete=models.CASCADE, related_name='related_links') name = models.CharField(max_length=255) url = models.URLField() panels = [ FieldPanel('name'), FieldPanel('url'), ] graphql_fields = [GraphQLField('name'), GraphQLField('url')]
class AuthorPageSnsLink(GraphQLEnabledModel, Orderable): Author = ParentalKey(AuthorPage, on_delete=models.CASCADE, related_name='sns_links', verbose_name=u'SNSなどのURL') name = models.CharField(verbose_name=u'名前', max_length=255) url = models.URLField(verbose_name=u'URL', ) panels = [ FieldPanel('name'), FieldPanel('url'), ] graphql_fields = [GraphQLField('name'), GraphQLField('url')]
class AuthorPageAmazonWishList(GraphQLEnabledModel, Orderable): Author = ParentalKey(AuthorPage, on_delete=models.CASCADE, related_name='amazon_wish_list_links', verbose_name=u'AmazonのほしいものリストのURL') name = models.CharField(verbose_name=u'名前', max_length=255) url = models.URLField(verbose_name=u'URL', ) panels = [ FieldPanel('name'), FieldPanel('url'), ] graphql_fields = [GraphQLField('name'), GraphQLField('url')]
def resolve_model_fields_for(self, model): """ Discover GraphQL fields definition for a particular model. """ assert model not in self._model_fields, ( f"{model}'s fields have been already registered." ) raw_fields = tuple(getattr(model, 'graphql_fields', tuple())) fields = collections.OrderedDict() for raw_field in raw_fields: if not isinstance(raw_field, GraphQLField): raise ValueError( 'Field must be a GraphQLField instance, not ' f'{type(raw_field)}' ) assert raw_field.name not in fields, ( f'{raw_field.name} for {model} is duplicated.' ) fields[raw_field.name] = raw_field # Add an ID field if it is not defined. if not any( f for f in fields.keys() if f in (model._meta.pk.name, 'pk') ): fields[model._meta.pk.name] = GraphQLField(model._meta.pk.name) self._model_fields[model] = tuple(fields.values())
class GalleryPage(GraphQLEnabledModel, Page): """ This is a page to list locations from the selected Collection. We use a Q object to list any Collection created (/admin/collections/) even if they contain no items. In this demo we use it for a GalleryPage, and is intended to show the extensibility of this aspect of Wagtail """ introduction = models.TextField(help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Landscape mode only; horizontal width between 1000px and ' '3000px.') body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) collection = models.ForeignKey( Collection, limit_choices_to=~models.Q(name__in=['Root']), null=True, blank=True, on_delete=models.SET_NULL, help_text='Select the image collection for this gallery.') content_panels = Page.content_panels + [ FieldPanel('introduction', classname="full"), StreamFieldPanel('body'), ImageChooserPanel('image'), FieldPanel('collection'), ] # Defining what content type can sit under the parent. Since it's a blank # array no subpage can be added subpage_types = [] graphql_fields = [ GraphQLField('introduction'), GraphQLField('body'), GraphQLField('image'), GraphQLField('collection'), ]
class CharacterPage(GraphQLEnabledModel, Page): """A page of character list.""" nickname = models.CharField(u"称号", max_length=15, null=True) name = models.CharField(u"名前", max_length=35, null=True) character_id = models.CharField(u"キャラクターID", max_length=6, null=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'画像', ) description = models.CharField(u'概要', max_length=255, null=True) introduction = MarkdownField(verbose_name=u"説明", null=True) game_name = models.CharField(u"登録されているゲーム", max_length=20, null=True) character_page_url = models.CharField(u"キャラクターのページ", max_length=255, null=True) content_panels = Page.content_panels + [ FieldPanel('nickname'), FieldPanel('name'), FieldPanel('character_id'), ImageChooserPanel('image'), FieldPanel('description'), MarkdownPanel('introduction'), FieldPanel('game_name'), FieldPanel('character_page_url'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField("nickname"), GraphQLField("name"), GraphQLField("character_id"), GraphQLField("image"), GraphQLField("description"), GraphQLField("introduction"), GraphQLField("game_name"), GraphQLField("character_page_url"), ] def clean(self): super().clean() new_title = '%s(%s)' % (self.name, self.character_id) new_slug = '%s' % self.character_id self.title = new_title self.slug = slugify(new_slug)
class SitePolicyPage(GraphQLEnabledModel, Page): body = MarkdownField(verbose_name=u'本文', blank=True) content_panels = Page.content_panels + [ MarkdownPanel("body", classname="full"), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField('body'), ]
class BookIndexPage(GraphQLEnabledModel, Page): intro = MarkdownField(null=True) def child_pages(self): return BookPage.objects.live().child_of(self) content_panels = Page.content_panels + [ MarkdownPanel('intro', classname='full') ] graphql_fields = [ GraphQLField('intro'), ] subpage_types = ['BookPage']
class HappinessPage(GraphQLEnabledModel, Page): date = models.DateField(u"投稿日") first = models.CharField(u"一つ目のよいこと", max_length=25) second = models.CharField(u"二つ目のよいこと", max_length=25) third = models.CharField(u"三つ目のよいこと", max_length=25) feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'画像') author = models.ForeignKey( 'author.AuthorPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'著者', ) content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('first'), FieldPanel('second'), FieldPanel('third'), PageChooserPanel('author', 'author.AuthorPage'), ImageChooserPanel('feed_image'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField('author'), GraphQLField('date'), GraphQLField('slug'), GraphQLField('first'), GraphQLField('second'), GraphQLField('third'), GraphQLField('feed_image') ] def clean(self): super().clean() new_title = '%s の3つのよいこと' % self.date new_slug = '3 good things in %s' % self.date self.title = new_title self.slug = slugify(new_slug)
class ArticlePage(GraphQLEnabledModel, Page): """Article Pages""" date = models.DateTimeField(u"投稿日") tags = ClusterTaggableManager(verbose_name=u'タグ', through=ArticlePageTag, blank=True) body = MarkdownField(verbose_name=u'本文', blank=True) feed_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'画像', ) author = models.ForeignKey( 'author.AuthorPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'著者', ) content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('tags'), MarkdownPanel("body", classname="full"), ImageChooserPanel('feed_image'), PageChooserPanel('author', 'author.AuthorPage'), InlinePanel('related_links', label=u'関連リンク'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField('author'), GraphQLField('date'), GraphQLField('tags'), GraphQLField('slug'), GraphQLField('body'), GraphQLField('feed_image'), GraphQLField('related_links') ]
class BookPage(GraphQLEnabledModel, Page): price = models.IntegerField() published_date = models.DateField() published_event = models.CharField(max_length=50) description = MarkdownField(verbose_name=u'説明') booth_url = models.URLField(max_length=255, null=True) bookwalker_url = models.URLField(max_length=255, null=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) content_panels = Page.content_panels + [ FieldPanel('price'), FieldPanel('published_date'), FieldPanel('published_event'), MarkdownPanel('description'), FieldPanel('booth_url'), FieldPanel('bookwalker_url'), ImageChooserPanel('image'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField('price'), GraphQLField('published_date'), GraphQLField('published_event'), GraphQLField('description'), GraphQLField('booth_url'), GraphQLField('bookwalker_url'), GraphQLField('image'), GraphQLField('slug') ]
class PortfolioPage(GraphQLEnabledModel, Page): update_date = models.DateField(u"更新日") tech = ClusterTaggableManager(verbose_name=u'経験または興味のある技術', through=PortfolioPageTechnology, blank=True) github_url = models.URLField(u'GitHubの個人ページ', max_length=200, blank=True) author = models.ForeignKey( 'author.AuthorPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'著者', ) content_panels = Page.content_panels + [ FieldPanel('update_date'), FieldPanel('tech'), FieldPanel('github_url'), InlinePanel('job_career', label=u'職務経歴'), InlinePanel('related_links', label=u'関連リンク'), PageChooserPanel('author', 'author.AuthorPage'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField('author'), GraphQLField('update_date'), GraphQLField('tech'), GraphQLField('github_url'), GraphQLField('job_career'), GraphQLField('related_links'), GraphQLField('slug'), ] def clean(self): super().clean() new_title = '%s のポートフォリオ' % self.author.name new_slug = self.author.slug self.title = new_title self.slug = slugify(new_slug)
class FooterText(GraphQLEnabledModel, models.Model): """ This provides editable text for the site footer. Again it uses the decorator `register_snippet` to allow it to be accessible via the admin. It is made accessible on the template via a template tag defined in base/templatetags/ navigation_tags.py """ body = RichTextField() panels = [ FieldPanel('body'), ] graphql_fields = [ GraphQLField('body'), ] def __str__(self): return "Footer text" class Meta: verbose_name_plural = 'Footer Text'
class BlogIndexPage(GraphQLEnabledModel, RoutablePageMixin, Page): """ Index page for blogs. We need to alter the page model's context to return the child page objects, the BlogPage objects, so that it works as an index page RoutablePageMixin is used to allow for a custom sub-URL for the tag views defined above. """ introduction = models.TextField(help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text= 'Landscape mode only; horizontal width between 1000px and 3000px.') graphql_fields = [ GraphQLField('introduction'), GraphQLField('image'), ] content_panels = Page.content_panels + [ FieldPanel('introduction', classname="full"), ImageChooserPanel('image'), ] # Speficies that only BlogPage objects can live under this index page subpage_types = ['BlogPage'] # Defines a method to access the children of the page (e.g. BlogPage # objects). On the demo site we use this on the HomePage def children(self): return self.get_children().specific().live() # Overrides the context to list all child items, that are live, by the # date that they were published # http://docs.wagtail.io/en/latest/getting_started/tutorial.html#overriding-context def get_context(self, request): context = super(BlogIndexPage, self).get_context(request) context['posts'] = BlogPage.objects.descendant_of( self).live().order_by('-date_published') return context # This defines a Custom view that utilizes Tags. This view will return all # related BlogPages for a given Tag or redirect back to the BlogIndexPage. # More information on RoutablePages is at # http://docs.wagtail.io/en/latest/reference/contrib/routablepage.html @route('^tags/$', name='tag_archive') @route('^tags/([\w-]+)/$', name='tag_archive') def tag_archive(self, request, tag=None): try: tag = Tag.objects.get(slug=tag) except Tag.DoesNotExist: if tag: msg = 'There are no blog posts tagged with "{}"'.format(tag) messages.add_message(request, messages.INFO, msg) return redirect(self.url) posts = self.get_posts(tag=tag) context = {'tag': tag, 'posts': posts} return render(request, 'blog/blog_index_page.html', context) def serve_preview(self, request, mode_name): # Needed for previews to work return self.serve(request) # Returns the child BlogPage objects for this BlogPageIndex. # If a tag is used then it will filter the posts by tag. def get_posts(self, tag=None): posts = BlogPage.objects.live().descendant_of(self) if tag: posts = posts.filter(tags=tag) return posts # Returns the list of Tags for all child posts of this BlogPage. def get_child_tags(self): tags = [] for post in self.get_posts(): # Not tags.append() because we don't want a list of lists tags += post.get_tags tags = sorted(set(tags)) return tags
class BlogPage(GraphQLEnabledModel, Page): """ A Blog Page We access the People object with an inline panel that references the ParentalKey's related_name in BlogPeopleRelationship. More docs: http://docs.wagtail.io/en/latest/topics/pages.html#inline-models """ introduction = models.TextField(help_text='Text to describe the page', blank=True) image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text= 'Landscape mode only; horizontal width between 1000px and 3000px.') body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) subtitle = models.CharField(blank=True, max_length=255) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) date_published = models.DateField("Date article published", blank=True, null=True) content_panels = Page.content_panels + [ FieldPanel('subtitle', classname="full"), FieldPanel('introduction', classname="full"), ImageChooserPanel('image'), StreamFieldPanel('body'), FieldPanel('date_published'), InlinePanel('blog_person_relationship', label="Author(s)", panels=None, min_num=1), FieldPanel('tags'), ] search_fields = Page.search_fields + [ index.SearchField('body'), ] graphql_fields = [ GraphQLField('introduction'), GraphQLField('image'), GraphQLField('body'), GraphQLField('tags'), GraphQLField('subtitle'), GraphQLField('date_published'), GraphQLField('authors', graphql_type=graphene.Field( lazy_queryset_list('base.People')), resolve_func=lambda self, info, **kwargs: self.authors()), ] def authors(self): """ Returns the BlogPage's related People. Again note that we are using the ParentalKey's related_name from the BlogPeopleRelationship model to access these objects. This allows us to access the People objects with a loop on the template. If we tried to access the blog_person_ relationship directly we'd print `blog.BlogPeopleRelationship.None` """ authors = [n.people for n in self.blog_person_relationship.all()] return authors @property def get_tags(self): """ Similar to the authors function above we're returning all the tags that are related to the blog post into a list we can access on the template. We're additionally adding a URL to access BlogPage objects with that tag """ tags = self.tags.all() for tag in tags: tag.url = '/' + '/'.join( s.strip('/') for s in [self.get_parent().url, 'tags', tag.slug]) return tags # Specifies parent to BlogPage as being BlogIndexPages parent_page_types = ['BlogIndexPage'] # Specifies what content types can exist as children of BlogPage. # Empty list means that no child content types are allowed. subpage_types = []
class AuthorPage(GraphQLEnabledModel, Page): profile = MarkdownField(verbose_name=u'プロフィール') nickname = models.CharField(verbose_name=u'ニックネーム', max_length=25, null=True, blank=True) first_name = models.CharField(verbose_name=u'名', max_length=10, null=True, blank=True) middle_name = models.CharField(verbose_name=u'ミドルネーム', max_length=10, null=True, blank=True) family_name = models.CharField(verbose_name=u'姓', max_length=10, null=True, blank=True) name = models.CharField(verbose_name=u'表示名', max_length=80, null=True, blank=True) is_surname_first = models.BooleanField(verbose_name=u'姓が先の表記', null=True, blank=True) use_nickname = models.BooleanField(verbose_name=u'ニックネームの使用', null=True, blank=True) portrait = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=u'画像', ) interest = ClusterTaggableManager(verbose_name=u'興味を持っていること', through=AuthorPageInterest, blank=True) content_panels = Page.content_panels + [ MarkdownPanel('profile'), ImageChooserPanel('portrait'), FieldPanel('interest'), FieldPanel('nickname'), FieldPanel('first_name'), FieldPanel('middle_name'), FieldPanel('family_name'), FieldPanel('name'), FieldPanel('is_surname_first'), FieldPanel('use_nickname'), InlinePanel('portfolio_links', label=u'ポートフォリオ'), InlinePanel('amazon_wish_list_links', label=u'Amazonのほしいものリスト'), InlinePanel('sns_links', label=u'SNSなどのリンク'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] graphql_fields = [ GraphQLField('name'), GraphQLField('profile'), GraphQLField('portrait'), GraphQLField('interest'), GraphQLField('portfolio_links'), GraphQLField('amazon_wish_list_links'), GraphQLField('sns_links'), ] def clean(self): """Rename title when posted.""" if self.nickname is not None and self.use_nickname is True: self.name = self.nickname elif self.is_surname_first is True: if self.middle_name is None: self.name = self.family_name + u' ' + self.first_name else: self.name = (self.family_name + u' ' + self.middle_name + u' ' + self.first_name) else: if self.middle_name is None: self.name = self.first_name + u' ' + self.family_name else: self.name = (self.first_name + u' ' + self.middle_name + u' ' + self.family_name) self.title = '%s のプロフィール' % self.name
class HomePage(GraphQLEnabledModel, Page): """ The Home Page. This looks slightly more complicated than it is. You can see if you visit your site and edit the homepage that it is split between a: - Hero area - Body area - A promotional area - Moveable featured site sections """ # Hero section of HomePage image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Homepage image') hero_text = models.CharField( max_length=255, help_text='Write an introduction for the bakery') hero_cta = models.CharField(verbose_name='Hero CTA', max_length=255, help_text='Text to display on Call to Action') hero_cta_link = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Hero CTA link', help_text='Choose a page to link to for the Call to Action') # Body section of the HomePage body = StreamField(BaseStreamBlock(), verbose_name="Home content block", blank=True) # Promo section of the HomePage promo_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Promo image') promo_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') promo_text = RichTextField(null=True, blank=True, help_text='Write some promotional copy') # Featured sections on the HomePage # You will see on templates/base/home_page.html that these are treated # in different ways, and displayed in different areas of the page. # Each list their children items that we access via the children function # that we define on the individual Page models e.g. BlogIndexPage featured_section_1_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_1 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='First featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 1') featured_section_2_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_2 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Second featured section for the homepage. Will display up to ' 'three child items.', verbose_name='Featured section 2') featured_section_3_title = models.CharField( null=True, blank=True, max_length=255, help_text='Title to display above the promo copy') featured_section_3 = models.ForeignKey( 'wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Third featured section for the homepage. Will display up to ' 'six child items.', verbose_name='Featured section 3') content_panels = Page.content_panels + [ MultiFieldPanel([ ImageChooserPanel('image'), FieldPanel('hero_text', classname="full"), MultiFieldPanel([ FieldPanel('hero_cta'), PageChooserPanel('hero_cta_link'), ]) ], heading="Hero section"), MultiFieldPanel([ ImageChooserPanel('promo_image'), FieldPanel('promo_title'), FieldPanel('promo_text'), ], heading="Promo section"), StreamFieldPanel('body'), MultiFieldPanel([ MultiFieldPanel([ FieldPanel('featured_section_1_title'), PageChooserPanel('featured_section_1'), ]), MultiFieldPanel([ FieldPanel('featured_section_2_title'), PageChooserPanel('featured_section_2'), ]), MultiFieldPanel([ FieldPanel('featured_section_3_title'), PageChooserPanel('featured_section_3'), ]) ], heading="Featured homepage sections", classname="collapsible") ] graphql_fields = [ GraphQLField('image'), GraphQLField('hero_text'), GraphQLField('hero_cta'), GraphQLField('hero_cta_link'), GraphQLField('promo_image'), GraphQLField('promo_title'), GraphQLField('promo_text'), GraphQLField('body'), GraphQLField('featured_section_1_title'), GraphQLField('featured_section_1'), GraphQLField('featured_section_2_title'), GraphQLField('featured_section_2'), GraphQLField('featured_section_3_title'), GraphQLField('featured_section_3'), ] def __str__(self): return self.title