class Resource(Page): base_form_class = ResourcePageForm document = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, max_length=500, related_name='+') thumbnail = models.ForeignKey('core.AffixImage', null=True, blank=True, on_delete=models.SET_NULL, max_length=500, related_name='+') date = models.DateField(blank=True, null=True) show_day = models.BooleanField(default=True) show_month = models.BooleanField(default=True) show_year = models.BooleanField(default=True) content = StreamField([ ("authors", blocks.RichTextBlock(blank=True)), ("copyright", blocks.RichTextBlock(blank=True)), ("focus", blocks.RichTextBlock(blank=True)), ("factoids", blocks.RichTextBlock(blank=True)), ]) rooms = ParentalManyToManyField('resources.Room', blank=True, related_name='resources') racks = ParentalManyToManyField('resources.Rack', blank=True, related_name='resources') subjects = ParentalManyToManyField('resources.Subject', related_name='resources', blank=True) type = models.ForeignKey('resources.ResourceType', related_name='resources', null=True, blank=True, on_delete=models.SET_NULL) categories = ParentalManyToManyField("category.Category", related_name="resources_by_category", blank=True) language = models.CharField(max_length=7, choices=settings.LANGUAGES) tags = ClusterTaggableManager(through=ResourceTag, blank=True) promote_panels = Page.promote_panels + [ FieldPanel('tags'), ] search_fields = Page.search_fields + [ index.SearchField('title', partial_match=True, boost=SearchBoost.TITLE), index.SearchField('language'), index.SearchField( 'content', partial_match=True, boost=SearchBoost.CONTENT), index.FilterField('date'), index.FilterField('get_categories'), index.FilterField('language'), index.FilterField('get_search_type'), ] def get_search_type(self): return self.__class__.__name__.lower() def get_categories(self): return [category.name for category in self.categories.all()] def get_absolute_url(self): return reverse("resource-detail", kwargs={"slug": self.slug}) def get_template(self, request, *args, **kwargs): self.template = "resources/resource_detail.html" return super(Resource, self).get_template(request, *args, **kwargs) def get_context(self, request, *args, **kwargs): return {'resource': self, 'request': request} content_panels = Page.content_panels + [ DocumentChooserPanel('document'), ImageChooserPanel('thumbnail'), FieldPanel('language'), StreamFieldPanel('content'), FieldPanel('rooms', widget=forms.SelectMultiple(attrs={'size': 10})), FieldPanel('racks', widget=forms.SelectMultiple(attrs={'size': 10})), # FieldPanel('subjects'), # FieldPanel('type'), FieldPanel('categories', widget=forms.SelectMultiple(attrs={'size': 10})), MultiFieldPanel([ FieldPanel('date'), FieldRowPanel([ FieldPanel('show_day', classname="col4"), FieldPanel('show_month', classname="col4"), FieldPanel('show_year', classname="col4") ]) ], 'Date'), ] @property def featured_image(self): return None def __str__(self): return self.title
class BlogPage(Page): subtitle = models.CharField(blank=True, max_length=255) date_published = models.DateField("Date article published", blank=True, null=True) introduction = models.CharField(max_length=250) body = StreamField(BaseStreamBlock(), verbose_name="Page body", blank=True) tags = ClusterTaggableManager( verbose_name='Tags', through=BlogPageTag, blank=True, related_name='posts_en', help_text='A comma-separated list of tags for the english'\ ' version.' ) tags_es = ClusterTaggableManager( verbose_name='Tags [ES] (Etiquetas)', through=BlogPageTagES, blank=True, related_name='posts_es', help_text='Listado separado por comas de etiquetas para la'\ ' versión en español.', ) def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None search_fields = Page.search_fields + [ index.SearchField('title'), index.SearchField('subtitle'), index.SearchField('introduction'), index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('subtitle', classname='full'), FieldPanel('introduction', classname='full'), StreamFieldPanel('body'), MultiFieldPanel([ FieldPanel('date_published'), FieldPanel('tags'), FieldPanel('tags_es'), ], heading='Blog information'), InlinePanel('blog_person_relationship', label=_("Author(s)"), panels=None, min_num=1), InlinePanel('gallery_images', label='Gallery images'), ] 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 = getattr(self, get_suffixed_string('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 = [] def get_year_month_hierarchy(self): qset = BlogPage.objects.descendant_of( self.get_parent()).live().order_by('-date_published').values_list( 'date_published', flat=True) pairs = list(set(map(lambda x: (x.year, x.month), qset))) result = [] for pair in pairs: if not result: result.append({'year': pair[0], 'months': []}) if not [x for x in result if x['year'] == pair[0]]: result.append({'year': pair[0], 'months': []}) idx = [result.index(x) for x in result if x['year'] == pair[0]][0] result[idx]['months'].append(pair[1]) result[idx]['months'] = list(set(result[idx]['months'])) return result def get_context(self, request): context = super(BlogPage, self).get_context(request) context['dates'] = self.get_year_month_hierarchy() return context class Meta: ordering = ('-date_published', )
class RestaurantPage(Page): tags = ClusterTaggableManager(through='tests.TaggedRestaurant', blank=True) content_panels = Page.content_panels + [ FieldPanel('tags'), ]
class Article(BasePage): # IMPORTANT: EACH ARTICLE is NOW LABELLED "POST" IN THE FRONT END resource_type = "article" # If you change this, CSS will need updating, too parent_page_types = ["Articles"] subpage_types = [] template = "article.html" class Meta: verbose_name = "post" # NB verbose_name_plural = "posts" # NB # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) body = CustomStreamField(help_text=( "The main post content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets")) related_links = StreamField( StreamBlock([("link", ExternalLinkBlock())], required=False), blank=True, null=True, help_text="Optional links further reading", verbose_name="Related links", ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=400, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta fields date = DateField( "Post date", default=datetime.date.today, help_text="The date the post was published", ) authors = StreamField( StreamBlock( [ ("author", PageChooserBlock(target_model="people.Person")), ("external_author", ExternalAuthorBlock()), ], required=False, ), blank=True, null=True, help_text=( "Optional list of the post's authors. Use ‘External author’ to add " "guest authors without creating a profile on the system"), ) keywords = ClusterTaggableManager(through=ArticleTag, blank=True) # Content panels content_panels = BasePage.content_panels + [ FieldPanel("description"), MultiFieldPanel( [ImageChooserPanel("image")], heading="Image", help_text= ("Optional header image. If not specified a fallback will be used. " "This image is also shown when sharing this page via social media" ), ), StreamFieldPanel("body"), StreamFieldPanel("related_links"), ] # Card panels card_panels = [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ] # Meta panels meta_panels = [ FieldPanel("date"), StreamFieldPanel("authors"), MultiFieldPanel( [InlinePanel("topics")], heading="Topics", help_text= ("The topic pages this post will appear on. The first topic in the " "list will be treated as the primary topic and will be shown in the " "page’s related content."), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and description " "for SEO purposes"), ), ] # Settings panels settings_panels = BasePage.settings_panels + [FieldPanel("slug")] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) def get_absolute_url(self): # For the RSS feed return self.full_url @property def primary_topic(self): """Return the first (primary) topic specified for the Article.""" article_topic = self.topics.first() return article_topic.topic if article_topic else None @property def read_time(self): return str(readtime.of_html(str(self.body))) @property def related_resources(self): """Returns resources that are related to the current resource, i.e. live, public Articles and Videos which have the same Topics.""" topic_pks = [topic.topic.pk for topic in self.topics.all()] return get_combined_articles_and_videos( self, topics__topic__pk__in=topic_pks) @property def month_group(self): return self.date.replace(day=1) def has_author(self, person): for author in self.authors: # pylint: disable=not-an-iterable if author.block_type == "author" and str(author.value) == str( person.title): return True return False
class HomePage(BasePage): subpage_types = [ "articles.Articles", "content.ContentPage", "events.Events", "people.People", "topics.Topics", "videos.Videos", ] template = "home.html" # Content fields subtitle = TextField(max_length=250, blank=True, default="") button_text = CharField(max_length=30, blank=True, default="") button_url = CharField(max_length=2048, blank=True, default="") image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", ) featured = StreamField( StreamBlock( [ ( "post", PageChooserBlock( target_model=( "articles.Article", "externalcontent.ExternalArticle", ) ), ), ( "content_page", PageChooserBlock(target_model=("content.ContentPage",)), ), ("external_page", FeaturedExternalBlock()), ( "video", PageChooserBlock( target_model=( "videos.Video", # NB: ExternalVideo is NOT allowed on the homepage # "externalcontent.ExternalVideo" ) ), ), ], min_num=2, max_num=5, required=False, ), null=True, blank=True, help_text=( "Optional space for featured posts, videos or links, min. 2, max. 5. " "Note that External Video is NOT allowed here." ), ) featured_people = StreamField( StreamBlock( [("person", PageChooserBlock(target_model="people.Person"))], max_num=3, required=False, ), null=True, blank=True, help_text="Optional featured people, max. 3", ) about_title = TextField(max_length=250, blank=True, default="") about_subtitle = TextField(max_length=250, blank=True, default="") about_button_text = CharField(max_length=30, blank=True, default="") about_button_url = URLField(max_length=140, blank=True, default="") # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField( "Description", max_length=DESCRIPTION_MAX_LENGTH, blank=True, default="" ) card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", ) # Meta fields keywords = ClusterTaggableManager(through=HomePageTag, blank=True) # Editor panel configuration content_panels = BasePage.content_panels + [ MultiFieldPanel( [ FieldPanel("subtitle"), # DISABLED - header needs a design tweak to fit in a button robustly # FieldPanel("button_text"), # FieldPanel("button_url"), ], heading="Header section", help_text="Optional fields for the header section", ), MultiFieldPanel( [ImageChooserPanel("image")], heading="Image", help_text=( "Optional image shown when sharing this page through social media" ), ), StreamFieldPanel("featured"), StreamFieldPanel("featured_people"), MultiFieldPanel( [ FieldPanel("about_title"), FieldPanel("about_subtitle"), FieldPanel("about_button_text"), FieldPanel("about_button_url"), ], heading="About section", help_text="Optional section to explain more about Mozilla", ), ] # Card panels card_panels = [ MultiFieldPanel( [ FieldPanel("card_title"), FieldPanel("card_description"), ImageChooserPanel("card_image"), ], heading="Card overrides", help_text=( ( "Optional fields to override the default title, " "description and image when this page is shown as a card" ) ), ) ] # Meta panels meta_panels = [ MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default " "title and description for SEO purposes" ), ) ] # Settings panels settings_panels = [FieldPanel("slug")] # Tabs edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] ) @classmethod def can_create_at(cls, parent): # Allow only one instance of this page type return super().can_create_at(parent) and not cls.objects.exists() @property def primary_topics(self): """The site’s top-level topics, i.e. topics without a parent topic.""" from ..topics.models import Topic return Topic.published_objects.filter(parent_topics__isnull=True)
class Place(ClusterableModel): name = models.CharField(max_length=255) tags = ClusterTaggableManager(through=TaggedPlace, blank=True) def __str__(self): return self.name
class BlogPage(MetadataPageMixin, Page): date = models.DateField("Post date", default=models.fields.datetime.date.today) intro = models.CharField(max_length=250) body = RichTextField(blank=True) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) show_in_homepage_slider = models.BooleanField( verbose_name='show in homepage slider', default=False ) author = models.ForeignKey( 'blog.AuthorPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) video = models.ForeignKey( 'wagtail_embed_videos.EmbedVideo', verbose_name="Video", null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) def get_context(self, request): # Update context to include only published posts, ordered by reverse-chron context = super().get_context(request) blogpages = BlogPage.objects.live().order_by('-first_published_at')[:3] context['blogpages'] = blogpages return context def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None def greek_date(self): return format_date(self.date, locale='el_GR') def get_meta_image(self): """A relevant Wagtail Image to show. Optional.""" gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None def get_meta_description(self): """ A short text description of this object. This should be plain text, not HTML. """ return self.intro search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('body'), index.SearchField('author'), index.SearchField('tags'), ] content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('date'), FieldPanel('tags'), FieldPanel('show_in_homepage_slider'), PageChooserPanel('author'), ], heading="Post information"), FieldPanel('intro'), FieldPanel('body'), EmbedVideoChooserPanel('video'), InlinePanel('gallery_images', label="Gallery images"), ] class Meta: verbose_name = 'Post' verbose_name_plural = 'Posts'
class BanneredCampaignPage(PrimaryPage): """ title, header, intro, and body are inherited from PrimaryPage """ # Note that this is a different related_name, as the `page` # name is already taken as back-referenced to CampaignPage. cta = models.ForeignKey( 'Petition', related_name='bcpage', blank=True, null=True, on_delete=models.SET_NULL, help_text='Choose an existing, or create a new, pettition form') signup = models.ForeignKey( 'Signup', related_name='bcpage', blank=True, null=True, on_delete=models.SET_NULL, help_text='Choose an existing, or create a new, sign-up form') tags = ClusterTaggableManager(through=BanneredCampaignTag, blank=True) panel_count = len(PrimaryPage.content_panels) n = panel_count - 1 content_panels = PrimaryPage.content_panels[:n] + [ SnippetChooserPanel('cta'), SnippetChooserPanel('signup'), ] + PrimaryPage.content_panels[n:] promote_panels = FoundationMetadataPageMixin.promote_panels + [ FieldPanel('tags'), ] translatable_fields = [ # Promote tab fields SynchronizedField('slug'), TranslatableField('seo_title'), SynchronizedField('show_in_menus'), TranslatableField('search_description'), SynchronizedField('search_image'), # Content tab fields TranslatableField('header'), TranslatableField('intro'), TranslatableField('body'), TranslatableField("title"), SynchronizedField("banner"), SynchronizedField("narrowed_page_content"), SynchronizedField("zen_nav"), TranslatableField("cta"), TranslatableField("signup"), ] subpage_types = [ 'BanneredCampaignPage', 'RedirectingPage', 'PublicationPage', 'OpportunityPage', 'ArticlePage' ] show_in_menus_default = True def get_context(self, request): context = super().get_context(request) context['related_posts'] = get_content_related_by_tag(self) return get_page_tree_information(self, context) class Meta: verbose_name = "Banner Page" verbose_name_plural = "Banner pages"
class ArticlePage(RoutablePageMixin, Page): sum_deck = models.CharField(max_length=1000) content = RichTextField(blank=True) yes_no = { ('y', 'Yes'), ('n', 'No'), } draft = models.CharField(max_length=2, choices=yes_no, blank=True, default='n') section_list = ( ('unews', 'University News'), ('metro', 'Metro'), ('sr', 'Science & Research'), ('ac', 'Arts & Culture'), ('sports', 'Sports'), ('sportscol', 'Sports Columns'), ('opinion', 'Opinion'), ('col', 'Columns'), ('edit', 'Editorials'), ('letter', 'Letters to the Editor'), ('notes', "Editors' Note"), ('mult', 'Multimedia'), ('vid', 'Video'), ('gal', 'Photo Gallery'), ('igraph', 'Interactive Graphic'), ('graph', 'Graphics'), ('ill', 'Illustrations'), ('op', 'Op-eds'), ('data', 'Data Science'), ('com', 'Comics'), # add more to this list ) featured_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) tags = ClusterTaggableManager(through=ArticleTag) section = models.CharField(max_length=8, choices=section_list, blank=True, default='h') nums = { ('1', 'Primary'), ('2', 'Secondary'), ('3', 'Tertiary'), } featured_on_main = models.CharField(max_length=2, choices=yes_no, blank=True, default='n') position_on_main = models.CharField(max_length=1, choices=nums, blank=True) api_fields = [ APIField('sum_deck'), APIField('content'), APIField('draft'), APIField('section'), APIField('featured_on_main'), APIField('position_on_main'), APIField('tags'), APIField('authors'), APIField('featured_image'), APIField('gallery_images') ] content_panels = Page.content_panels + [ FieldPanel('section', classname='class'), FieldPanel('sum_deck', classname='class'), FieldPanel('draft', classname='class'), FieldPanel('featured_on_main', classname='class'), FieldPanel('position_on_main', classname='class'), FieldPanel('tags'), InlinePanel('authors', heading='authors', help_text='Add contributing authors'), FieldPanel('content', classname='class'), ImageChooserPanel('featured_image', classname='image'), InlinePanel('gallery_images', label="Photo Gallery Images"), ] search_fields = Page.search_fields + [ index.SearchField('content'), index.SearchField('section'), index.SearchField('sum_deck'), index.SearchField('tags'), index.SearchField('authors'), ]
class CoursePage(Page): #database fields date = models.DateField("Post Date") name = models.CharField(max_length=200) intro = models.CharField(max_length=250) students = models.ManyToManyField(User) body = RichTextField(blank=True) feed_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') tags = ClusterTaggableManager(through=CoursePageTag, blank=True) #body = RichTextField(blank=True) # search index configuration search_fields = Page.search_fields + [ index.SearchField('name'), index.SearchField('intro'), index.SearchField('body'), index.FilterField('date'), ] # editor panels configuration content_panels = Page.content_panels + [ MultiFieldPanel([ FieldPanel('date'), FieldPanel('tags'), ], heading='Course information'), FieldPanel('name'), FieldPanel('intro'), FieldPanel('body', classname="full"), InlinePanel('gallery_images', label="Gallery images"), InlinePanel('related_links', label="Related links"), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ImageChooserPanel('feed_image'), ] # parent page/subpage type rules parent_page_types = ['courses.CourseIndexPage'] subpage_types = ['courses.SectionPage'] def get_absolute_url(self): return reverse('course_detail', args=(self.id, )) def __str__(self): return self.name def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None def get_context(self, request): # Update context to include only published posts, ordered by reverse-chron = .order_by('-first_published_at') context = super(CoursePage, self).get_context(request) sections = self.get_children().live().order_by('first_published_at') context['sections'] = sections units = [] #print sections for s in sections: units.append(s.specific.number) units = list(set(units)) context['units'] = units return context
class People(Page): parent_page_types = ['home.HomePage', 'content.ContentPage'] subpage_types = ['Person'] template = 'people.html' # Content fields description = TextField( blank=True, default='', help_text='Optional short text description, max. 400 characters', max_length=400, ) # Meta fields keywords = ClusterTaggableManager(through=PeopleTag, blank=True) # Content panels content_panels = Page.content_panels + [ FieldPanel('description'), ] # Meta panels meta_panels = [ MultiFieldPanel( [ FieldPanel('seo_title'), FieldPanel('search_description'), FieldPanel('keywords'), ], heading='SEO', help_text= 'Optional fields to override the default title and description for SEO purposes' ), ] # Settings panels settings_panels = [ FieldPanel('slug'), FieldPanel('show_in_menus'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(meta_panels, heading='Meta'), ObjectList(settings_panels, heading='Settings', classname='settings'), ]) class Meta: verbose_name_plural = 'People' @classmethod def can_create_at(cls, parent): # Allow only one instance of this page type return super().can_create_at(parent) and not cls.objects.exists() def get_context(self, request): context = super().get_context(request) context['filters'] = self.get_filters() return context @property def people(self): return Person.objects.all().public().live().order_by('title') def get_filters(self): from ..topics.models import Topic return { 'roles': True, 'topics': Topic.objects.live().public().order_by('title'), }
class Person(Page): resource_type = 'person' parent_page_types = ['People'] subpage_types = [] template = 'person.html' # Content fields job_title = CharField(max_length=250) role = CharField(max_length=250, choices=ROLE_CHOICES, default='staff') description = RichTextField( 'About', blank=True, default='', features=RICH_TEXT_FEATURES, help_text='Optional ‘About me’ section content, supports rich text', ) image = ForeignKey( 'mozimages.MozImage', null=True, blank=True, on_delete=SET_NULL, related_name='+', ) # Card fields card_title = CharField('Title', max_length=140, blank=True, default='') card_description = TextField('Description', max_length=400, blank=True, default='') card_image = ForeignKey( 'mozimages.MozImage', null=True, blank=True, on_delete=SET_NULL, related_name='+', verbose_name='Image', ) # Meta twitter = CharField(max_length=250, blank=True, default='') facebook = CharField(max_length=250, blank=True, default='') linkedin = CharField(max_length=250, blank=True, default='') github = CharField(max_length=250, blank=True, default='') email = CharField(max_length=250, blank=True, default='') websites = StreamField( StreamBlock([('website', PersonalWebsiteBlock())], max_num=3, required=False), null=True, blank=True, help_text='Optional links to any other personal websites', ) keywords = ClusterTaggableManager(through=PersonTag, blank=True) # Content panels content_panels = [ MultiFieldPanel([ CustomLabelFieldPanel('title', label='Full name'), FieldPanel('job_title'), FieldPanel('role'), ], heading='Details'), FieldPanel('description'), MultiFieldPanel( [ ImageChooserPanel('image'), ], heading='Image', help_text= ('Optional header image. If not specified a fallback will be used. This image is also shown when sharing ' 'this page via social media')), ] # Card panels card_panels = [ FieldPanel('card_title'), FieldPanel('card_description'), ImageChooserPanel('card_image'), ] # Meta panels meta_panels = [ MultiFieldPanel([ InlinePanel('topics'), ], heading='Topics interested in'), MultiFieldPanel([ FieldPanel('twitter'), FieldPanel('facebook'), FieldPanel('linkedin'), FieldPanel('github'), FieldPanel('email'), ], heading='Profiles', help_text=''), StreamFieldPanel('websites'), MultiFieldPanel( [ FieldPanel('seo_title'), FieldPanel('search_description'), FieldPanel('keywords'), ], heading='SEO', help_text= 'Optional fields to override the default title and description for SEO purposes' ), ] # Settings panels settings_panels = [ FieldPanel('slug'), ] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(card_panels, heading='Card'), ObjectList(meta_panels, heading='Meta'), ObjectList(settings_panels, heading='Settings', classname='settings'), ]) @property def events(self): ''' Return upcoming events where this person is a speaker, ordered by start date ''' from ..events.models import Event upcoming_events = (Event.objects.filter( start_date__gte=datetime.datetime.now()).live().public()) speaker_events = Event.objects.none() for event in upcoming_events.all(): # add the event to the list if the current person is a speaker if event.has_speaker(self): speaker_events = speaker_events | Event.objects.page(event) return speaker_events.order_by('start_date') @property def articles(self): ''' Return articles and external articles where this person is (one of) the authors, ordered by article date, most recent first ''' from ..articles.models import Article from ..externalcontent.models import ExternalArticle articles = Article.objects.none() external_articles = ExternalArticle.objects.none() all_articles = Article.objects.live().public().all() all_external_articles = ExternalArticle.objects.live().public().all() for article in all_articles: if article.has_author(self): articles = articles | Article.objects.page(article) for external_article in all_external_articles: if external_article.has_author(self): external_articles = external_articles | ExternalArticle.objects.page( external_article) return sorted(chain(articles, external_articles), key=attrgetter('date'), reverse=True) @property def videos(self): ''' Return the most recent videos and external videos where this person is (one of) the speakers. ''' from ..videos.models import Video from ..externalcontent.models import ExternalVideo videos = Video.objects.none() external_videos = ExternalVideo.objects.none() all_videos = Video.objects.live().public().all() all_external_videos = ExternalVideo.objects.live().public().all() for video in all_videos: if video.has_speaker(self): videos = videos | Video.objects.page(video) for external_video in all_external_videos: if external_video.has_speaker(self): external_videos = external_videos | ExternalVideo.objects.page( external_video) return sorted(chain(videos, external_videos), key=attrgetter('date'), reverse=True) @property def role_group(self): return { 'slug': self.role, 'title': dict(ROLE_CHOICES).get(self.role, ''), }
class ContentPage(BasePage): parent_page_types = ["home.HomePage", "content.ContentPage", "topics.Topic"] subpage_types = ["people.People", "content.ContentPage"] template = "content.html" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) body = CustomStreamField( help_text=( "Main page body content. Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ) ) sidebar = CustomStreamField( null=True, blank=True, help_text=( "Sidebar page body content (narrower than main body). Rendered to the " "right of the main body content in desktop and below it in mobile." "Supports rich text, images, embed via URL, " "embed via HTML, and inline code snippets" ), ) # Card fields card_title = CharField("Title", max_length=140, blank=True, default="") card_description = TextField("Description", max_length=140, blank=True, default="") card_image = ForeignKey( "mozimages.MozImage", null=True, blank=True, on_delete=SET_NULL, related_name="+", verbose_name="Image", help_text="An image in 16:9 aspect ratio", ) # Meta fields nav_description = TextField( "Navigation description", max_length=400, blank=True, default="" ) icon = FileField( upload_to="contentpage/icons", blank=True, default="", help_text=( "MUST be a black-on-transparent SVG icon ONLY, " "with no bitmap embedded in it." ), validators=[check_for_svg_file], ) keywords = ClusterTaggableManager(through=ContentPageTag, blank=True) # Editor panel configuration content_panels = BasePage.content_panels + [ FieldPanel("description"), StreamFieldPanel("body"), StreamFieldPanel("sidebar"), ] # Card panels card_panels = [ FieldPanel( "card_title", help_text=( "Title displayed when this page is " "represented by a card in a list of items. " "If blank, the page's title is used." ), ), FieldPanel( "card_description", help_text=( "Summary text displayed when this page is " "represented by a card in a list of items. " "If blank, the page's description is used." ), ), MultiFieldPanel( [ImageChooserPanel("card_image")], heading="16:9 Image", help_text=( "Image used for representing this page as a Card. " "Should be 16:9 aspect ratio. " "If not specified a fallback will be used. " "This image is also shown when sharing this page via social " "media unless a social image is specified." ), ), ] # Meta panels meta_panels = [ FieldPanel( "nav_description", help_text="Text to display in the navigation with the title for this page.", ), MultiFieldPanel( [FieldPanel("icon")], heading="Theme", help_text=( "This icon will be used if, for example, this page is shown in a Menu" ), ), MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=( "Optional fields to override the default title and " "description for SEO purposes" ), ), ] # Settings panels settings_panels = BasePage.settings_panels + [ FieldPanel("slug"), FieldPanel("show_in_menus"), ] # Tabs edit_handler = TabbedInterface( [ ObjectList(content_panels, heading="Content"), ObjectList(card_panels, heading="Card"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ] )
class GenericPage(Page): """ GenericPage uses a Streamfield with a raw HTML block so is flexible. Used for leaf nodes where we don't want to show links to siblings, such as tool pages and standalone articles. """ date = models.DateField("Post date", null=True, blank=True) short_description = RichTextField(blank=True) github_link = models.URLField("Github link", blank=True) include_mathjax = models.BooleanField("Include mathjax") extra_js_code = models.TextField("Additional JS code", blank=True) show_siblings = models.BooleanField(default=False) tags = ClusterTaggableManager(through=GenericPageTag, blank=True) featured_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') body = StreamField([('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RichTextBlock()), ('html', blocks.RawHTMLBlock()), ('image', ImageChooserBlock()), ('code_block', CodeBlock()), ('two_columns', TwoColumnBlock()), ('link_block', PageLinksBlock())]) # Inherit search_fields from Page and add more search_fields = Page.search_fields + [ index.SearchField('short_description', boost="1.5"), index.SearchField('body'), ] content_panels = Page.content_panels + [ FieldPanel('date'), FieldPanel('show_siblings'), MultiFieldPanel([ FieldPanel('github_link'), FieldPanel('include_mathjax'), InlinePanel('css_links', label="CSS links"), InlinePanel('js_links', label="JS links"), FieldPanel('extra_js_code'), ], heading="Additional resources", classname="collapsible collapsed"), StreamFieldPanel('body'), ] promote_panels = [ MultiFieldPanel( [ ImageChooserPanel('featured_image'), FieldPanel('short_description'), ], heading="Featured content information", ) ] + Page.promote_panels + [FieldPanel('tags')] def get_context(self, request): context = super(GenericPage, self).get_context(request) if self.show_siblings: context["previous_page"] = self.get_prev_siblings().live().first() context["next_page"] = self.get_next_siblings().live().first() return context
class CommunityAsset(ClusterableModel): name = models.CharField(blank=False, null=True, max_length=200) parent_organisation = models.CharField(blank=True, null=True, max_length=200, help_text="The parent organisation delivering this service, if applicable") description = models.TextField(blank=False, null=True, max_length=500, help_text="Describe the service in a short paragraph") price = models.CharField(blank=True, null=True, max_length=100, help_text="Give a cost per session/activity. If a simple price cannot be given, leave the field blank.", verbose_name="Cost (£)") category = models.ForeignKey(Categories, on_delete=models.CASCADE, null=True, blank=False, default="") keywords = ClusterTaggableManager(through=Keywords, blank=True) age_groups = models.ManyToManyField(AgeGroups, blank=True) suitability = models.ManyToManyField(Suitabilities, blank=True) accessibility = models.ManyToManyField(Accessibilities, blank=True) venue = models.CharField(blank=True, null=True, max_length=150) area = models.CharField(blank=True, null=True, max_length=100) postcode = models.CharField(blank=True, null=True, max_length=100) # Wagtailgmaps expects a `CharField` (or any other field that renders as a text input) lat_lng = models.CharField(max_length=255, null=True) days = models.ManyToManyField(Days, blank=True) frequency = models.CharField(blank=True, null=True, max_length=150, help_text="Describe the frequency of this event if applicable. For example 'daily' or 'fortnightly'") daytime = models.BooleanField(blank=True, null=True, help_text="Does this service happen during the daytime (between 9-5)?", default=False, verbose_name="During daytime?") contact_name = models.CharField(blank=True, null=True, max_length=150, help_text="Give the name of a person involved with this service") email = models.EmailField(blank=True, null=True, max_length=100, help_text="Give a contact email address") phone = models.CharField(blank=True, null=True, max_length=100, help_text="Give a contact telephone number, with no spaces") url = models.URLField(blank=True, null=True, help_text="The website or webpage where this service can be booked, or where more info can be found about it", verbose_name="Website URL") review_notes = models.TextField(blank=True, null=True, max_length=500) assigned_to = models.TextField(blank=True, null=True, max_length=500) review_number = models.CharField(blank=True, null=True, max_length=100) review_status = models.ForeignKey(ReviewStatus, on_delete=models.CASCADE, null=True, blank=False, default="") last_updated = models.CharField(blank=True, null=True, max_length=100) review_date = models.CharField(blank=True, null=True, max_length=100) laf_areas = models.ForeignKey(LAFAreas, on_delete=models.CASCADE, null=True, blank=True, default="", verbose_name="LAF Area") ccg_locality = models.ForeignKey(CCGLocalities, on_delete=models.CASCADE, null=True, blank=True, default="", verbose_name="CCG Locality") vol_dbs_check = models.TextField(blank=True, null=True, max_length=500, verbose_name="Volunteer DBS check") safeguarding = models.TextField(blank=True, null=True, max_length=500) health_safety = models.TextField(blank=True, null=True, max_length=500) insurance = models.TextField(blank=True, null=True, max_length=500) clo_notes = models.TextField(blank=True, null=True, max_length=500, verbose_name="CLO notes") legacy_categories = models.ManyToManyField(LegacyCategories, blank=True) panels = [ MultiFieldPanel([ FieldPanel('name'), FieldPanel('parent_organisation'), FieldPanel('description'), FieldPanel('price'), ]), MultiFieldPanel([ FieldPanel('category', widget=Select), FieldPanel('keywords'), FieldPanel('age_groups', widget=CheckboxSelectMultiple), FieldPanel('suitability', widget=CheckboxSelectMultiple), FieldPanel('accessibility', widget=CheckboxSelectMultiple), ], heading="Discovery"), MultiFieldPanel([ FieldPanel('days', widget=CheckboxSelectMultiple), FieldRowPanel([ FieldPanel('daytime', widget=CheckboxInput), FieldPanel('frequency') ]), ], heading="When?"), MultiFieldPanel([ FieldPanel('venue'), FieldRowPanel([ FieldPanel('area'), FieldPanel('postcode') ]), MapFieldPanel('lat_lng', latlng=True), ], heading="Where?"), MultiFieldPanel([ FieldPanel('contact_name'), FieldRowPanel([ FieldPanel('phone'), FieldPanel('email') ]), FieldPanel('url'), ], heading="Contact details"), MultiFieldPanel([ FieldPanel('review_notes'), FieldRowPanel([ FieldPanel('assigned_to'), FieldPanel('review_number'), ]), FieldRowPanel([ FieldPanel('last_updated'), FieldPanel('review_date'), ]), FieldRowPanel([ FieldPanel('vol_dbs_check'), FieldPanel('safeguarding'), ]), FieldRowPanel([ FieldPanel('health_safety'), FieldPanel('insurance'), ]), FieldRowPanel([ FieldPanel('laf_areas', widget=Select), FieldPanel('ccg_locality', widget=Select), ]), FieldPanel('clo_notes'), FieldPanel('legacy_categories', widget=CheckboxSelectMultiple), ], heading="Internal") ] def __str__(self): return self.name
class ThesisPage(Page): description = RichTextField() why_important = RichTextField() sources = RichTextField() tags = ClusterTaggableManager(through=ThesisPageTag, blank=True) provider = models.ForeignKey(ThesisProvider, default=1, on_delete=models.SET_NULL, null=True) search_fields = Page.search_fields + [ index.SearchField('description'), index.SearchField('why_important'), index.SearchField('sources'), ] content_panels = Page.content_panels + [ FieldPanel('description'), FieldPanel('why_important'), FieldPanel('tags'), FieldPanel('sources'), SnippetChooserPanel('provider'), ] promote_panels = [ MultiFieldPanel(Page.promote_panels, "Common page configuration"), ] parent_page_types = ['theses.ThesisIndexPage'] def serve(self, request): if request.method == 'POST': from theses.forms import InterestsForm form = InterestsForm(request.POST) if form.is_valid(): form.clean() mail_content = self.build_mail_content(request.build_absolute_uri(), form.cleaned_data) send_mail('Thesis interest: {}'.format(request.POST['thesis_title']), mail_content, THESES_MAILS, # recipient email form.cleaned_data['contact_email'] ) return JsonResponse({'message': 'Thank you for your interest! ' 'We will let get back to you soon!'}) else: logger.error('The submitted form was invalid.') return JsonResponse({'message': 'Sorry, submitting your form was not ' 'successful. Please use our contact page.'}) else: return super(ThesisPage, self).serve(request) def get_context(self, request): from theses.forms import InterestsForm context = super(ThesisPage, self).get_context(request) context["contactForm"] = InterestsForm return context @staticmethod def build_mail_content(uri, data): return dedent(""" Thesis: {thesis_uri} Name: {contact_name}, Contact email: {contact_email}, Course and University: {course_and_university}, Deadline: {deadline} --------Message-------- {content} """.format(thesis_uri=uri, **data))
class PaperPage(Page): is_featured = models.BooleanField() cover_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) date = models.DateField("Published date") date_fmt = models.CharField("Date format", max_length=8, choices=DATEFMT, default='YMD') abstract = RichTextField(blank=True, null=True) extra = RichTextField(blank=True, null=True) tags = ClusterTaggableManager(through=PaperPageTag, blank=True) authors = models.TextField() journal = models.ForeignKey( 'publication.Journal', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) volume = models.CharField(max_length=64, blank=True, null=True) issue = models.CharField(max_length=64, blank=True, null=True) page = models.CharField(max_length=64, blank=True, null=True) doi = models.CharField(max_length=250, blank=True, null=True) permalink = models.URLField("Permanent link", blank=True, null=True) bibtex = models.TextField(blank=True, null=True) category = models.ForeignKey( 'publication.Category', null=True, blank=True, on_delete=models.PROTECT, related_name='+', ) content_panels = Page.content_panels + [ FieldPanel('is_featured'), ImageChooserPanel('cover_image'), FieldPanel('authors'), MultiFieldPanel([ FieldRowPanel([ FieldPanel('journal'), ]), FieldRowPanel([ FieldPanel('volume'), FieldPanel('issue'), FieldPanel('page'), ]), ], "Reference information"), FieldPanel('date_fmt'), FieldPanel('date'), FieldPanel('category'), FieldPanel('tags'), FieldPanel('doi'), FieldPanel('permalink'), FieldPanel('bibtex'), FieldPanel('abstract', classname="full"), FieldPanel('extra', classname="full"), ] subpage_types = [] parent_page_types = [ 'PapersIndexPage', ] def print_authors(self): return "{}, et al.".format(self.authors.split(';')[0])
class CaseStudy(ClusterableModel): """Dedicated snippet for use as a case study. Supports personalised selection via its tags. The decision about the appropriate Case Study block to show will happen when the page attempts to render the relevant CaseStudyBlock. Note that this is rendered via Wagtail's ModelAdmin, so appears in the sidebar, but we have to keep it registered as a Snippet to be able to transfer it with Wagtail-Transfer """ title = models.CharField( max_length=255, blank=False, verbose_name='Internal case study title', ) # old name company_name summary_context = models.CharField(max_length=255, blank=False, default='How we did it') # old name summary lead_title = models.TextField( blank=False) # Deliberately not rich-text / no formatting body = StreamField( [ ( 'media', blocks.StreamBlock( [ ('video', core_blocks.SimpleVideoBlock( template='core/includes/_case_study_video.html')), ('image', core_blocks.ImageBlock()), ], min_num=1, max_num=2, ), ), ( 'text', blocks.RichTextBlock(features=RICHTEXT_FEATURES__MINIMAL, ), ), ( 'quote', core_blocks.CaseStudyQuoteBlock(), ), ], validators=[case_study_body_validation], help_text= ('This block must contain one Media section (with one or two items in it) ' 'and/or Quote sections, then one Text section.'), ) # We are keeping the personalisation-relevant tags in separate # fields to aid lookup and make the UX easier for editors hs_code_tags = ClusterTaggableManager(through='core.HSCodeTaggedCaseStudy', blank=True, verbose_name='HS-code tags') country_code_tags = ClusterTaggableManager( through='core.CountryTaggedCaseStudy', blank=True, verbose_name='Country tags') region_code_tags = ClusterTaggableManager( through='core.RegionTaggedCaseStudy', blank=True, verbose_name='Region tags') trading_bloc_code_tags = ClusterTaggableManager( through='core.TradingBlocTaggedCaseStudy', blank=True, verbose_name='Trading bloc tags') created = CreationDateTimeField('created', null=True) modified = ModificationDateTimeField('modified', null=True) panels = [ MultiFieldPanel( [ FieldPanel('title'), FieldPanel('lead_title'), FieldPanel('summary_context'), StreamFieldPanel('body'), ], heading='Case Study content', ), MultiFieldPanel( [ FieldPanel('hs_code_tags'), FieldPanel('country_code_tags'), FieldPanel('region_code_tags'), FieldPanel('trading_bloc_code_tags'), ], heading='Case Study tags for Personalisation', ), MultiFieldPanel( [ InlinePanel('related_pages', label='Related pages'), ], heading= 'Related Lesson, Topic & Module, also used for Personalisation', ), ] def __str__(self): display_name = self.title if self.title else self.summary_context return f'{display_name}' def save(self, **kwargs): self.update_modified = kwargs.pop( 'update_modified', getattr(self, 'update_modified', True)) super().save(**kwargs) def get_cms_standalone_view_url(self): return reverse('cms_extras:case-study-view', args=[self.id]) class Meta: verbose_name_plural = 'Case studies' get_latest_by = 'modified' ordering = ( '-modified', '-created', )
class PresentationPage(Page): is_featured = models.BooleanField() cover_image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) date = models.DateField("Presentation date") date_fmt = models.CharField("Date format", max_length=8, choices=DATEFMT, default='YMD') abstract = RichTextField(blank=True, null=True) extra = RichTextField(blank=True, null=True) tags = ClusterTaggableManager(through=PresentationPageTag, blank=True) presentor = models.ForeignKey( 'home.Person', null=True, blank=True, on_delete=models.PROTECT, related_name='+', ) meeting = models.CharField(max_length=512) country = models.CharField(max_length=256, blank=True, null=True) location = models.CharField(max_length=256, blank=True, null=True) presentation_type = models.CharField(max_length=64, choices=PRESENTATIONTYPE, blank=True, null=True) category = models.ForeignKey( 'publication.Category', null=True, blank=True, on_delete=models.PROTECT, related_name='+', ) content_panels = Page.content_panels + [ FieldPanel('is_featured'), ImageChooserPanel('cover_image'), SnippetChooserPanel('presentor'), MultiFieldPanel([ FieldRowPanel([ FieldPanel('meeting', classname="col10"), ]), FieldRowPanel([ FieldPanel('country', classname="col6"), FieldPanel('location', classname="col6"), ]), FieldRowPanel([ FieldPanel('presentation_type', classname="col6"), FieldPanel('category', classname="col6"), ]) ], "Meeting information"), FieldPanel('date_fmt'), FieldPanel('date'), FieldPanel('tags'), FieldPanel('abstract', classname="full"), FieldPanel('extra', classname="full"), ] subpage_types = [] parent_page_types = [ 'PresentationsIndexPage', ]
class ArticlePage(Page): tags = ClusterTaggableManager(through=ArticlePageTag, blank=True) author_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') author_name = models.CharField(max_length=10, validators=[MinLengthValidator(2)]) article_datetime = models.DateTimeField() article_cover = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') article_cover_app = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') description = models.TextField(blank=True) liked_count = models.IntegerField(default=0) body = StreamField([ ('Paragraph', blocks.RichTextBlock()), ('RawHTML', blocks.RawHTMLBlock()), ('DocumentChooser', DocumentChooserBlock()), ]) content_panels = Page.content_panels + [ HelpPanel('建议文章标题不超过30个字'), FieldPanel('tags'), HelpPanel('建议标签数量最多2个,标签字数1~5个'), FieldPanel('description'), ImageChooserPanel('author_image'), FieldPanel('author_name'), FieldPanel('article_datetime'), ImageChooserPanel('article_cover'), ImageChooserPanel('article_cover_app'), StreamFieldPanel('body'), ] api_fields = [ APIField('tags'), APIField('author_image'), APIField('author_name'), APIField('article_datetime', serializer=DateTimeField(format="%Y-%m-%d %H:%M")), APIField('article_cover'), APIField('article_cover_app'), APIField('description'), APIField('liked_count'), # This will nest the relevant BlogPageAuthor objects in the API response ] parent_page_types = ['ArticleListPage'] subpage_types = [] def get_datetime_format(self): return self.article_datetime.strftime("%Y-%m-%d %H:%I") @property def first_tag(self): if self.tags: return self.tags.first() else: return None
class Topic(Page): resource_type = 'topic' parent_page_types = ['Topics'] subpage_types = ['Topic'] template = 'topic.html' # Content fields description = TextField(max_length=250, blank=True, default='') featured = StreamField( StreamBlock([ ('article', PageChooserBlock( required=False, target_model=[ 'articles.Article', 'externalcontent.ExternalArticle', ], )), ('external_page', FeaturedExternalBlock()), ], min_num=0, max_num=4, required=False), null=True, blank=True, ) get_started = StreamField( StreamBlock([('panel', GetStartedBlock())], min_num=0, max_num=3, required=False), null=True, blank=True, ) # Card fields card_title = CharField('Title', max_length=140, blank=True, default='') card_description = TextField('Description', max_length=140, blank=True, default='') card_image = ForeignKey( 'mozimages.MozImage', null=True, blank=True, on_delete=SET_NULL, related_name='+', verbose_name='Image', ) # Meta icon = FileField(upload_to='topics/icons', blank=True, default='') color = CharField(max_length=14, choices=COLOR_CHOICES, default='blue-40') keywords = ClusterTaggableManager(through=TopicTag, blank=True) # Content panels content_panels = Page.content_panels + [ FieldPanel('description'), StreamFieldPanel('featured'), StreamFieldPanel('get_started'), MultiFieldPanel([ InlinePanel('people'), ], heading='People'), ] # Card panels card_panels = [ FieldPanel('card_title'), FieldPanel('card_description'), ImageChooserPanel('card_image'), ] # Meta panels meta_panels = [ MultiFieldPanel( [ InlinePanel('parent_topics', label='Parent topic(s)'), InlinePanel('child_topics', label='Child topic(s)'), ], heading='Parent/child topic(s)', classname='collapsible collapsed', help_text=( 'Topics with no parent (i.e. top-level topics) will be listed ' 'on the home page. Child topics are listed on the parent ' 'topic’s page.')), MultiFieldPanel([ FieldPanel('icon'), FieldPanel('color'), ], heading='Theme'), MultiFieldPanel([ FieldPanel('seo_title'), FieldPanel('search_description'), FieldPanel('keywords'), ], heading='SEO'), ] # Settings panels settings_panels = [ FieldPanel('slug'), FieldPanel('show_in_menus'), ] # Tabs edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(card_panels, heading='Card'), ObjectList(meta_panels, heading='Meta'), ObjectList(settings_panels, heading='Settings', classname='settings'), ]) @property def articles(self): return get_combined_articles(self, topics__topic__pk=self.pk) @property def events(self): """Return upcoming events for this topic, ignoring events in the past, ordered by start date""" return get_combined_events(self, topics__topic__pk=self.pk, start_date__gte=datetime.datetime.now()) @property def color_value(self): return dict(COLOR_VALUES)[self.color] @property def subtopics(self): return [topic.child for topic in self.child_topics.all()]
class PracticePage(Page): parent_page_types = ['practice.PracticeIndexPage'] subpage_types = [] full_title = models.TextField("Полное название", max_length=800, null=True) raw_subtitle = RichTextField(verbose_name='Подзаголовок', blank=True) content = RichTextField(verbose_name='Содержание', blank=True) doc = models.ForeignKey( 'base.CustomDocument', blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name= "Документ") publish_date = models.DateField("Дата обнародования", null=True) category = ForeignKey('practice.CaseCategory', blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Категория дел") level = ForeignKey('practice.CourtLevel', blank=True, null=True, on_delete=models.SET_NULL, verbose_name="Уровень суда") tags = ClusterTaggableManager(through='practice.PracticePageTag', blank=True) @property def subtitle(self): return richtext(self.raw_subtitle) @property def text(self): return richtext(self.content) @property def breadcrumbs(self): breadcrumbs = [] for page in self.get_ancestors()[2:]: breadcrumbs.append({'title': page.title, 'url': page.url}) return breadcrumbs @property def section_title(self): return self.get_parent().title @property def section_url(self): return self.get_parent().url @property def tags_slugs(self): return '\n'.join(self.tags.all().values_list('slug', flat=True)) def get_sitemap_urls(self, request): return [{ 'location': self.full_url[:-1], 'lastmod': self.last_published_at, }] def clean(self): super().clean() # автоматически создаем слаг и заголовок if len(self.full_title) >= 254 : self.title = self.full_title[:254] else: self.title = self.full_title if self.slug == 'default-blank-slug': if self.doc: dot_index = self.doc.filename.rfind('.') filename = self.doc.filename[:dot_index] self.slug = slugify(filename) elif len(self.title) > 80: self.slug = slugify(self.title[:80]) else: self.slug = slugify(self.title) if '--' in self.slug: self.slug = self.slug.replace('--','-') if self.slug.endswith('-'): self.slug = self.slug[:-1] @property def clean_preview(self): h = html2text.HTML2Text() h.ignore_links = True h.ignore_emphasis = True h.ignore_images = True if self.subtitle: raw_text = h.handle(self.subtitle) else: raw_text = h.handle(self.raw_content) if len(raw_text) > 310: raw_text = raw_text[:310] space_index = raw_text.rfind(' ') raw_text = raw_text[:space_index] + '...' return raw_text.replace('\n', ' ').strip() search_fields = Page.search_fields + [ index.SearchField('full_title'), index.SearchField('subtitle'), index.SearchField('content'), index.SearchField('tags'), index.RelatedFields('tags', [ index.SearchField('slug'), index.FilterField('slug'), index.FilterField('name'), index.SearchField('name')]), index.SearchField('tags_slugs', partial_match=True), index.SearchField('category'), index.FilterField('level'), index.FilterField('category'), index.RelatedFields('category', [ index.FilterField('name'), ]), ] api_fields = [ APIField('full_title'), APIField('subtitle'), APIField('text'), APIField('doc', serializer=base_serializers.DocSerializer()), APIField('breadcrumbs'), APIField('section_title'), APIField('section_url'), APIField('publish_date', serializer=base_serializers.DateSerializer()), APIField('category', serializer=serializers.CategorySerializer()), APIField('level', serializer=serializers.LevelSerializer()), APIField('tags', serializer=base_serializers.TagSerializer()), APIField('related_docs', serializer=serializers.RelatedDocsSerializer()), APIField('clean_preview'), ] content_panels = [ FieldPanel('full_title'), FieldPanel('raw_subtitle'), FieldPanel('publish_date'), DocumentChooserPanel('doc'), FieldPanel('category', widget=forms.RadioSelect), FieldPanel('level', widget=forms.RadioSelect), FieldPanel('tags'), MultiFieldPanel([InlinePanel("related_docs", label='Документ') ], heading='Связанные документы' ), FieldPanel('content'), ] class Meta: verbose_name = 'Страница судебной практики' verbose_name_plural = 'Страницы судебной практики'
class BlogPage(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 = RichTextField(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'), FieldPanel('body', classname="full"), FieldPanel('date_published'), FieldPanel('tags'), ] search_fields = Page.search_fields + [ index.SearchField('body'), ] 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 TaggedPage(Page): tags = ClusterTaggableManager(through=TaggedPageTag, blank=True)
class BlogPage(Page): author = models.ForeignKey( 'core.AuthorPage', null=True, blank=True, on_delete=models.SET_NULL, ) publication_date = models.DateField( help_text="Past or future date of publication") summary = models.TextField(help_text="Intro or short summary of post") body = StreamField([ ( 'rich_text', blocks.RichTextBlock( icon='doc-full', label='Rich Text', features=[ # Default features 'h2', 'h3', 'h4', 'hr', 'link', 'bold', 'italic', 'ol', 'ul', 'document-link', 'embed', 'image', # Extra features 'code', ], )), ('code', CodeBlock(icon='code')), ('quote', QuoteBlock(icon='openquote')), ('markdown', MarkdownBlock()), ('html', blocks.RawHTMLBlock(icon='site', label='HTML')), ('image', CaptionedImageBlock()), ]) banner_image = models.ForeignKey("wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL) tags = ClusterTaggableManager(through=BlogPageTag, blank=True) teaser_image = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, help_text="Image to display on the blog index page", related_name="+") content_panels = Page.content_panels + [ PageChooserPanel('author'), FieldPanel('publication_date'), FieldPanel('summary'), StreamFieldPanel('body'), ImageChooserPanel('banner_image'), ] parent_page_types = ['BlogIndexPage'] promote_panels = Page.promote_panels + [ MultiFieldPanel([ ImageChooserPanel('teaser_image'), ], "Teaser Details"), FieldPanel('tags'), ] def get_context(self, request): context = super(BlogPage, self).get_context(request) context['blog_index'] = BlogIndexPage.objects.first() return context
class Articles(BasePage): RESOURCES_PER_PAGE = 6 # IMPORTANT: ARTICLES ARE NOW LABELLED "POSTS" IN THE FRONT END parent_page_types = ["home.HomePage"] subpage_types = ["Article"] template = "articles.html" class Meta: verbose_name = "posts" verbose_name_plural = "posts" # Content fields description = RichTextField( blank=True, default="", features=RICH_TEXT_FEATURES_SIMPLE, help_text="Optional short text description, max. 400 characters", max_length=400, ) # Meta fields keywords = ClusterTaggableManager(through=ArticlesTag, blank=True) # Content panels content_panels = BasePage.content_panels + [FieldPanel("description")] # Meta panels meta_panels = [ MultiFieldPanel( [ FieldPanel("seo_title"), FieldPanel("search_description"), ImageChooserPanel("social_image"), FieldPanel("keywords"), ], heading="SEO", help_text=("Optional fields to override the default title " "and description for SEO purposes"), ) ] # Settings panels settings_panels = BasePage.settings_panels + [ FieldPanel("slug"), FieldPanel("show_in_menus"), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Content"), ObjectList(meta_panels, heading="Meta"), ObjectList(settings_panels, heading="Settings", classname="settings"), ]) @classmethod def can_create_at(cls, parent): # Allow only one instance of this page type return super().can_create_at(parent) and not cls.objects.exists() def get_context(self, request): context = super().get_context(request) context["filters"] = self.get_filters() context["resources"] = self.get_resources(request) return context def get_resources(self, request): # This Page class will show both Articles/Posts and Videos in its listing # We can't use __in in this deeply related query, so we have to make # a custom Q object instead and pass is in as a filter, then deal with # it later topics = request.GET.getlist(TOPIC_QUERYSTRING_KEY) topics_q = Q(topics__topic__slug__in=topics) if topics else Q() resources = get_combined_articles_and_videos(self, q_object=topics_q) resources = paginate_resources( resources, page_ref=request.GET.get(PAGINATION_QUERYSTRING_KEY), per_page=self.RESOURCES_PER_PAGE, ) return resources def get_filters(self): from ..topics.models import Topic return {"topics": Topic.published_objects.order_by("title")}
class RecordPage(ContentPage): formatted_title = models.CharField(max_length=255, null=True, blank=True, default='', help_text="Use if you need italics in the title. e.g. <em>Italicized words</em>") date = models.DateField(default=datetime.date.today) category = models.CharField( max_length=255, choices=constants.record_page_categories.items() ) read_next = models.ForeignKey( 'RecordPage', blank=True, null=True, default=get_previous_record_page, on_delete=models.SET_NULL ) related_section_title = models.CharField( max_length=255, blank=True, default='Explore campaign finance data' ) related_section_url = models.CharField( max_length=255, blank=True, default='/data/' ) monthly_issue = models.CharField( max_length=255, blank=True, default='' ) monthly_issue_url = models.CharField( max_length=255, blank=True, default='' ) keywords = ClusterTaggableManager(through=RecordPageTag, blank=True) homepage_pin = models.BooleanField(default=False) homepage_pin_expiration = models.DateField(blank=True, null=True) homepage_pin_start = models.DateField(blank=True, null=True) homepage_hide = models.BooleanField(default=False) template = 'home/updates/record_page.html' content_panels = ContentPage.content_panels + [ FieldPanel('formatted_title'), FieldPanel('date'), FieldPanel('monthly_issue'), FieldPanel('category'), FieldPanel('keywords'), InlinePanel('authors', label='Authors'), PageChooserPanel('read_next'), FieldPanel('related_section_title'), FieldPanel('related_section_url') ] promote_panels = Page.promote_panels + [ MultiFieldPanel([ FieldPanel('homepage_pin'), FieldPanel('homepage_pin_start'), FieldPanel('homepage_pin_expiration'), FieldPanel('homepage_hide') ], heading="Home page feed" ) ] search_fields = ContentPage.search_fields + [ index.FilterField('category'), index.FilterField('date') ] @property def content_section(self): return '' @property def get_update_type(self): return constants.update_types['fec-record'] @property def get_author_office(self): return 'Information Division'