class BlogPage(MetadataPageMixin, Page): date = models.DateField("Post date") # intro = models.CharField(max_length=250) body = RichTextField(blank=True) # Categories node = ParentalManyToManyField('home.Node', blank=True) brand = models.CharField(max_length=100, blank=True, null=True) model = models.CharField(max_length=100, blank=True, null=True) weight = models.CharField(max_length=100, blank=True, null=True) color = models.CharField(max_length=50, blank=True, null=True) dimensions = models.CharField(max_length=100, blank=True, null=True) # Links affiliate_name = models.CharField(max_length=100, blank=True, null=True) affiliate_link = models.URLField(blank=True, null=True) # Social links facebook_link = models.URLField(blank=True, null=True) twitter_link = models.URLField(blank=True, null=True) pinterest_link = models.URLField(blank=True, null=True) youtube_link = models.URLField(blank=True, null=True) instagram_link = models.URLField(blank=True, null=True) # Method returns first element of the image gallery @property def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None @property def categories(self): categories = self.node.all() return categories @property def main_category(self): categories = self.categories if categories: category = categories.first() else: category = None return category def get_context(self, request): context = super().get_context(request) context['related_items'] = [ i.related_page for i in self.related_items.all() ] return context search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('brand'), index.SearchField('model'), index.SearchField('color'), index.SearchField('affiliate_name'), index.SearchField('node'), ] content_panels = Page.content_panels + [ FieldPanel('date'), # ImageChooserPanel('main_image'), # FieldPanel('intro'), FieldPanel('body'), FieldPanel('brand'), FieldPanel('model'), FieldPanel('weight'), FieldPanel('color'), FieldPanel('dimensions'), FieldPanel('affiliate_name'), FieldPanel('affiliate_link'), FieldPanel('facebook_link'), FieldPanel('twitter_link'), FieldPanel('pinterest_link'), FieldPanel('youtube_link'), FieldPanel('instagram_link'), InlinePanel('gallery_images', max_num=6, label="Gallery images"), FieldPanel('node', widget=forms.CheckboxSelectMultiple), InlinePanel('adv_block', max_num=1, label="Adv Side Block"), InlinePanel('related_items', max_num=3, label="Related Items"), ]
class Sessions(models.Model): SESSION_TYPE_CHOICES = [('lecture', 'Vorlesung'), ('exercise', 'Übung'), ('study_group', 'Arbeitsgemeinschaft'), ('exam_prep', 'Klausurenkurs'), ('seminar', 'Seminar')] SEMESTER_TYPE_CHOICES = [ ('ss2022', 'Sommersemester 2022'), ('ws2022', 'Wintersemester 2022'), ('ss2021', 'Sommersemester 2021'), ('ws2021', 'Wintersemester 2021'), ('ss2020', 'Sommersemester 2020'), ('ws2020', 'Wintersemester 2020'), ('ss2019', 'Sommersemester 2019'), ('ws2019', 'Wintersemester 2019'), ('ss2018', 'Sommersemester 2018'), ('ws2018', 'Wintersemester 2018'), ] name = models.CharField(max_length=255) subtitle = models.CharField(max_length=255) date = RichTextField(blank=True) #tags = ClusterTaggableManager(through=SessionTags, blank=True) speaker = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') type = models.CharField(choices=SESSION_TYPE_CHOICES, default='lecture', max_length=255, blank=True) semester = models.CharField(choices=SEMESTER_TYPE_CHOICES, default='ws2020', max_length=255, blank=True) assessment = RichTextField(blank=True) description = RichTextField(blank=True) speaker_description = RichTextField(blank=True) content = RichTextField(blank=True) location = RichTextField(blank=True) lat = models.FloatField(null=True, blank=True) lon = models.FloatField(null=True, blank=True) panels = [ MultiFieldPanel([ FieldPanel('name', classname="col-12"), FieldPanel('subtitle', classname="col-12"), ], "Session"), MultiFieldPanel([ FieldPanel('date', classname="col-12"), FieldPanel('type', classname="col-12"), FieldPanel('semester', classname="col-12"), FieldPanel('description', classname="col-12"), FieldPanel('assessment', classname="col-12"), ], "Info"), MultiFieldPanel([ FieldPanel('speaker', classname="col-12"), FieldPanel('speaker_description', classname="col-12"), ], "Speaker"), MultiFieldPanel([ FieldPanel('location', classname="col-12"), FieldPanel('lat', classname="col-6"), FieldPanel('lon', classname="col-6"), ], "Location"), ] search_fields = [ index.SearchField('title'), index.SearchField('speaker'), index.SearchField('description'), ] class Meta: verbose_name = 'Lehrveranstaltung' verbose_name_plural = 'Lehrveranstaltungen'
class SpotlightIndicator(Page): class Meta(): verbose_name = 'Spotlight Indicator' parent_page_types = [SpotlightTheme] ddw_id = models.CharField(max_length=255) description = models.TextField(help_text='A description of this indicator', null=True, blank=True) source = models.TextField(help_text='Where is the data from?', null=True, blank=True) color = models.ForeignKey( SpotlightColour, null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) start_year = models.IntegerField(blank=True, null=True) end_year = models.IntegerField(blank=True, null=True) excluded_years = models.TextField(help_text='Comma separated values e.g. 2002,2004,2017', blank=True, null=True) DATA_FORMAT_CHOICES = [ ('percent', 'Percentage'), ('plain', 'Plain'), ('currency', 'Currency') ] data_format = models.TextField( max_length=100, choices=DATA_FORMAT_CHOICES, default='plain', help_text='Options are plain, currency, percent') range = models.CharField(max_length=100, null=True, blank=True, help_text='The range of values shown on the legend') value_prefix = models.CharField(max_length=100, null=True, blank=True) value_suffix = models.CharField(max_length=100, null=True, blank=True) tooltip_template = models.TextField( blank=True, null=True, help_text='Text for the tooltip.Template strings can be used to substitute values e.g. {name}') config = StreamField( AceEditorStreamBlock(max_num=1, block_counts={'JSON': {'max_num':1}}), null=True, blank=True, verbose_name='JSON Config') content_panels = Page.content_panels + [ FieldPanel('ddw_id'), FieldPanel('description'), FieldPanel('source'), SnippetChooserPanel('color'), FieldPanel('start_year'), FieldPanel('end_year'), FieldPanel('excluded_years'), FieldPanel('data_format'), FieldPanel('range'), FieldPanel('value_prefix'), FieldPanel('value_suffix'), FieldPanel('tooltip_template'), StreamFieldPanel('config') ] search_fields = Page.search_fields + [ index.SearchField('ddw_id') ] def serve(self, request, *args, **kwargs): raise Http404() def serve_preview(self, request, mode_name): return self.get_parent().serve(request)
class ConferencePage(PublicBasePage, SocialMediaFields): """ Main page for creating conferences. """ # Generic variables hex_regex = RegexValidator( regex='^#[a-zA-Z0-9]{6}$', message='Please enter a hex color, e.g. #012043' ) # Field definitions primary_branding_color = models.CharField( validators=[hex_regex], max_length=7, blank=True ) secondary_branding_color = models.CharField( validators=[hex_regex], max_length=7, blank=True ) location = models.ForeignKey( 'public.LocationPage', null=True, blank=True, on_delete=models.SET_NULL, related_name='%(app_label)s_%(class)s_related' ) start_date = models.DateTimeField(blank=True, null=True) end_date = models.DateTimeField(blank=True, null=True) current = models.BooleanField( default=True, help_text="Uncheck this when the conference has transpired" ) conference_logo = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) secondary_registration_heading = models.CharField(max_length=40, blank=True) secondary_registration_description = models.TextField(blank=True) body = StreamField(DefaultBodyFields()) # Panels and subpage types content_panels = Page.content_panels + [ MultiFieldPanel( [ ImageChooserPanel('banner_image'), FieldPanel('banner_title'), FieldPanel('banner_subtitle'), ], heading='Banner' ), MultiFieldPanel( [ FieldPanel('primary_branding_color'), FieldPanel('secondary_branding_color'), ImageChooserPanel('conference_logo'), ], heading='Branding', ), MultiFieldPanel( [ FieldPanel('start_date'), FieldPanel('end_date'), ], heading='Dates and Times', ), FieldPanel('location'), FieldPanel('current'), InlinePanel('main_registration', label='Main Registration Link'), MultiFieldPanel( [ FieldPanel('secondary_registration_heading'), FieldPanel('secondary_registration_description'), InlinePanel( 'sub_registration', label='Secondary Registration Link' ), ], heading='Secondary Registration Links', ), InlinePanel('sponsors', label='Sponsors'), InlinePanel('organizers', label='Organizers'), StreamFieldPanel('body'), ] + SocialMediaFields.panels + PublicBasePage.content_panels subpage_types = [ 'conferences.ConferencePage', 'conferences.ConferenceSubPage', 'redirects.RedirectPage' ] search_fields = PublicBasePage.search_fields + [ index.SearchField('banner_image'), index.SearchField('primary_branding_color'), index.SearchField('secondary_branding_color'), index.SearchField('location'), index.SearchField('current'), index.SearchField('conference_logo'), index.SearchField('body'), ] api_fields = [ APIField('body'), ] @property def has_right_sidebar(self): """ Test to see if a right sidebar should be displayed. Returns: Boolean """ fields = [ self.sponsors, self.organizers, self.sub_registration, self.secondary_registration_heading, self.secondary_registration_description ] return self.base_has_right_sidebar() or self.has_field(fields) def has_conf_banner(self, current_site): """ Used to override the boolean [0] value for PublicBasePage get_banner. Args: current_site: object """ return self.get_banner(current_site)[0] or ( self.primary_branding_color and self.banner_title ) def get_banner(self, current_site): """ Override the default get_banner method so that banners will always display as long as a title is present. Args: current_site: site object. Returns: See get_banner in PublicBasePage. """ try: # Base case if self.banner_title: return ( True, self.banner_image, self.banner_feature, self.banner_title, self.banner_subtitle, self.relative_url(current_site), self.title ) # Recursive case else: return self.get_parent().specific.get_banner(current_site) # Reached the top of the tree (could factor this into an if) except (AttributeError): return (False, None, None, '', '', '', '') # Context def get_context(self, request): context = super(ConferencePage, self).get_context(request) current_site = Site.find_for_request(request) main_reg = self.main_registration.all() has_sidebar = self.has_left_sidebar(context) or bool(main_reg) context['has_left_sidebar'] = has_sidebar context['content_div_css'] = self.get_conditional_css_classes( 'content', has_sidebar ) context['breadcrumb_div_css'] = self.get_conditional_css_classes( 'breadcrumbs', has_sidebar ) context['has_banner'] = self.has_conf_banner(current_site) context['primary_branding_color'] = self.primary_branding_color context['secondary_branding_color'] = self.secondary_branding_color context['conference_logo'] = self.conference_logo context['conference_title'] = self.title context['has_social_media'] = self.has_social_media context['main_registration'] = main_reg context['sponsors'] = self.sponsors.all() context['organizers'] = self.organizers.all() context['secondary_registration'] = self.sub_registration.all() context['secondary_registration_heading' ] = self.secondary_registration_heading context['secondary_registration_description' ] = self.secondary_registration_description context['home'] = self.relative_url(current_site) return context
class MemorialPath(I18nPage): """A list of memorials.""" parent_page_types = ['MemorialPathIndex'] description = RichTextField( features=I18nPage.RICH_TEXT_FEATURES, blank=True, verbose_name=_(TXT['memorial_path.description']), help_text=_(TXT['memorial_path.description.help']), ) description_de = RichTextField( features=I18nPage.RICH_TEXT_FEATURES, blank=True, verbose_name=_(TXT['memorial_path.description']), help_text=_(TXT['memorial_path.description.help']), ) description_cs = RichTextField( features=I18nPage.RICH_TEXT_FEATURES, blank=True, verbose_name=_(TXT['memorial_path.description']), help_text=_(TXT['memorial_path.description.help']), ) i18n_description = TranslatedField.named('description') search_fields = I18nPage.search_fields + [ index.SearchField('description'), index.SearchField('description_de'), index.SearchField('description_cs'), ] api_fields = I18nPage.api_fields + [ APIField('description'), APIField('description_de'), APIField('description_cs'), ] general_panels = [ FieldPanelTabs(children=[ FieldPanelTab('title', heading=_(TXT['language.en'])), FieldPanelTab('title_de', heading=_(TXT['language.de'])), FieldPanelTab('title_cs', heading=_(TXT['language.cs'])), ], heading=_(TXT['page.title'])), FieldPanelTabs(children=[ FieldPanelTab('description', heading=_(TXT['language.en'])), FieldPanelTab('description_de', heading=_(TXT['language.de'])), FieldPanelTab('description_cs', heading=_(TXT['language.cs'])), ], heading=_(TXT['memorial_path.description'])), MultiFieldPanel(children=[ InlinePanel('waypoints', panels=[ PageChooserPanel('memorial'), ]), ], heading=_(TXT['memorial_path_waypoint.plural'])), ] meta_panels = I18nPage.meta_panels + [FieldPanel("slug")] edit_handler = TabbedInterface([ ObjectList(general_panels, heading=_(TXT["heading.general"])), ObjectList(meta_panels, heading=_(TXT["heading.meta"])), ]) class Meta: db_table = DB_TABLE_PREFIX + 'memorial_path' verbose_name = _(TXT['memorial_path']) verbose_name_plural = _(TXT['memorial_path.plural'])
class ApteanRespondCaseFormPage(BasePage): template = "patterns/pages/cases/form_page.html" landing_page_template = "patterns/pages/cases/form_page_landing.html" form = models.CharField(max_length=255, choices=APTEAN_FORM_CHOICES) introduction = RichTextField( blank=True, help_text="Text displayed before the form", features=RICH_TEXT_FEATURES, ) pre_submission_text = RichTextField( blank=True, help_text="Text displayed after the form, above the submit button", features=RICH_TEXT_FEATURES, verbose_name="pre-submission text", ) completion_title = models.CharField( max_length=255, help_text= "Heading for the page shown after successful form submission.", ) completion_content = RichTextField( blank=True, help_text= "Text displayed to the user on successful submission of the form", features=RICH_TEXT_FEATURES, ) action_text = models.CharField( max_length=32, blank=True, help_text='Form action button text. Defaults to "Submit"', ) search_fields = BasePage.search_fields + [ index.SearchField("introduction") ] content_panels = BasePage.content_panels + [ FieldPanel("form"), FieldPanel("introduction"), FieldPanel("pre_submission_text"), FieldPanel("action_text"), MultiFieldPanel( [FieldPanel("completion_title"), FieldPanel("completion_content")], "Confirmation page", ), ] def get_form_class(self): return APTEAN_FORM_MAPPING[self.form] def get_form(self, *args, **kwargs): form_class = self.get_form_class() return form_class(*args, **kwargs) def serve(self, request, *args, **kwargs): try: if request.method == "POST": form = self.get_form(request.POST, request.FILES) if form.is_valid(): form, case_reference = self.process_form_submission(form) if form.is_valid(): # still return self.render_landing_page( request, case_reference, *args, **kwargs) else: form = self.get_form() except RespondClientException: form = None context = self.get_context(request) context["form"] = form context["form_template"] = form.template_name return render(request, self.get_template(request), context) def process_form_submission(self, form): case_xml = form.get_xml_string() client = get_client() response = client.create_case(form.webservice, case_xml) soup = BeautifulSoup(response.content, "xml") if response.status_code != 200: reverse_schema_mapping = { v: k for k, v in form.field_schema_name_mapping.items() } for error in soup.find_all("failure"): if ("schemaName" in error.attrs and error.attrs["schemaName"] in reverse_schema_mapping): form.add_error( reverse_schema_mapping[error.attrs["schemaName"]], error.text) elif ("type" in error.attrs and error.attrs["type"] == ATTACHMENT_FAILURE_ERROR): form.add_error(ATTACHMENT_SCHEMA_NAME, error.text) else: form.add_error(None, error.text) return form, None else: case_reference = get_case_reference(soup) return form, case_reference def get_landing_page_template(self, request, *args, **kwargs): return self.landing_page_template def render_landing_page(self, request, case_reference=None, *args, **kwargs): """ Renders the landing page. You can override this method to return a different HttpResponse as landing page. E.g. you could return a redirect to a separate page. """ context = self.get_context(request) context["case_reference"] = case_reference return render(request, self.get_landing_page_template(request), context)
class BlogPage(Page): """ This is the core of the Blog app. BlogPage are individual articles """ subtitle = models.CharField(blank=True, max_length=255) body = MarkdownField(blank=True) extended_body = StreamField( [('content', MarkdownBlock(template='blog/markdown_block.html')), ('newsletter', NewsletterSubscribe()), ('book', BookInline()), ('video', EmbedBlock())], null=True) excerpt = models.TextField( blank=True, null=True, help_text='Short text to display in lists of posts') image = models.ForeignKey( 'wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Header Image, used also for social sharing') image_data = RichTextField( blank=True, null=True, help_text= 'Information about the header image, to appear after the article') video = models.TextField( blank=True, null=True, help_text= 'If the article has an associated video, it will appear as the header') tags = ClusterTaggableManager(through=BlogPageTag, blank=True) date_published = models.DateField("Date article published", blank=True, null=True) allow_comments = models.BooleanField(default=True) content_panels = Page.content_panels + [ FieldPanel('subtitle'), StreamFieldPanel('extended_body'), FieldPanel('excerpt'), MarkdownPanel('body'), ImageChooserPanel('image'), FieldPanel('image_data'), FieldPanel('video'), FieldPanel('date_published'), InlinePanel('blog_person_relationship', label="Author(s)", panels=None, min_num=1), FieldPanel('tags'), ] promote_panels = Page.promote_panels + [ FieldPanel('allow_comments'), ] 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 first_author(self): return self.authors()[-1] @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 = [] def get_absolute_url(self): return self.specific.url def get_context(self, request): context = super(BlogPage, self).get_context(request) context['latest_articles'] = BlogPage.objects.live().order_by( '-date_published')[:5] context['intro_class'] = 'blog article' return context @property def introduction(self): if self.excerpt: return self.excerpt html = render_markdown(self.body) soup = BeautifulSoup(html, "html.parser") try: introduction = soup.find('p').text return introduction except AttributeError: return None class Meta: ordering = ['-date_published']
class LocationPage(Page): """ Detail for a specific bakery location. """ 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) address = models.TextField() lat_long = models.CharField( max_length=36, help_text="Comma separated lat/long. (Ex. 64.144367, -21.939182) \ Right click Google Maps and select 'What\'s Here'", validators=[ RegexValidator( regex=r'^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$', message= 'Lat Long must be a comma-separated numeric lat and long', code='invalid_lat_long'), ]) # Search index configuration search_fields = Page.search_fields + [ index.SearchField('address'), index.SearchField('body'), ] # Fields to show to the editor in the admin view content_panels = [ FieldPanel('title', classname="full"), FieldPanel('introduction', classname="full"), ImageChooserPanel('image'), StreamFieldPanel('body'), FieldPanel('address', classname="full"), FieldPanel('lat_long'), InlinePanel('hours_of_operation', label="Hours of Operation"), ] api_fields = [ APIField('title'), APIField('introduction'), APIField('image'), APIField('body'), APIField('address'), APIField('lat_long'), APIField('hours_of_operation'), ] def __str__(self): return self.title @property def operating_hours(self): hours = self.hours_of_operation.all() return hours # Determines if the location is currently open. It is timezone naive def is_open(self): now = datetime.now() current_time = now.time() current_day = now.strftime('%a').upper() try: self.operating_hours.get(day=current_day, opening_time__lte=current_time, closing_time__gte=current_time) return True except LocationOperatingHours.DoesNotExist: return False # Makes additional context available to the template so that we can access # the latitude, longitude and map API key to render the map def get_context(self, request): context = super(LocationPage, self).get_context(request) context['lat'] = self.lat_long.split(",")[0] context['long'] = self.lat_long.split(",")[1] context['google_map_api_key'] = settings.GOOGLE_MAP_API_KEY return context # Can only be placed under a LocationsIndexPage object parent_page_types = ['LocationsIndexPage']
class BlogPage(Page): body = RichTextField(verbose_name=_('body'), blank=True) tags = ClusterTaggableManager(through=BlogPageTag, 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(get_image_model_string(), 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=limit_author_choices, verbose_name=_('Author'), on_delete=models.SET_NULL, related_name='author_pages', ) search_fields = Page.search_fields + [ index.SearchField('body'), ] blog_categories = models.ManyToManyField(BlogCategory, through=BlogCategoryBlogPage, blank=True) 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(BlogPage, self).save_revision(*args, **kwargs) def get_absolute_url(self): return self.url def get_blog_index(self): # Find closest ancestor which is a blog index return self.get_ancestors().type(BlogIndexPage).last() def get_context(self, request, *args, **kwargs): context = super(BlogPage, self).get_context(request, *args, **kwargs) context['blogs'] = self.get_blog_index().blogindexpage.blogs context = get_blog_context(context) context['COMMENTS_APP'] = COMMENTS_APP return context class Meta: verbose_name = _('Blog page') verbose_name_plural = _('Blog pages') parent_page_types = ['blog.BlogIndexPage']
class GlossaryTerm(index.Indexed, models.Model): name_en = models.CharField( max_length=255, verbose_name='TERM (ENGLISH)' ) definition_en = RichTextField( null=True, blank=True, verbose_name='DEFINITION (ENGLISH)' ) anchor_en = models.CharField( max_length=255, null=True, blank=True, verbose_name='ANCHOR SLUG (ENGLISH)' ) answer_page_en = models.ForeignKey( 'ask_cfpb.AnswerPage', on_delete=models.CASCADE, related_name='glossary_terms', null=True, blank=True, verbose_name='ANSWER PAGE (ENGLISH)' ) name_es = models.CharField( max_length=255, null=True, blank=True, verbose_name='TERM (SPANISH)' ) definition_es = RichTextField( null=True, blank=True, verbose_name='DEFINITION (SPANISH)' ) anchor_es = models.CharField( max_length=255, null=True, blank=True, verbose_name='ANCHOR SLUG (SPANISH)' ) answer_page_es = models.ForeignKey( 'ask_cfpb.AnswerPage', on_delete=models.CASCADE, related_name='glossary_terms_es', null=True, blank=True, verbose_name='ANSWER PAGE (SPANISH)' ) portal_topic = ParentalKey( 'v1.PortalTopic', related_name='glossary_terms', null=True, blank=True ) search_fields = [ index.SearchField('name_en', partial_match=True), index.SearchField('definition_en', partial_match=True), index.SearchField('name_es', partial_match=True), index.SearchField('definition_es', partial_match=True), ] def name(self, language='en'): if language == 'es': return self.name_es return self.name_en def definition(self, language='en'): if language == 'es': return self.definition_es return self.definition_en def answer_page_url(self, language='en'): if language == 'es' and self.answer_page_es: return self.answer_page_es.url if self.answer_page_en: return self.answer_page_en.url return None def anchor(self, language='en'): if language == 'es': return self.anchor_es return self.anchor_en def __str__(self): return self.name_en class Meta: unique_together = ['portal_topic', 'name_en']
class AnswerPage(CFGOVPage): """Page type for Ask CFPB answers.""" from ask_cfpb.models.django 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).")) short_answer = RichTextField(blank=True, features=['link', 'document-link'], help_text='Optional answer intro') answer_content = StreamField(ask_blocks.AskAnswerContent(), blank=True, verbose_name='Answer') answer_base = models.ForeignKey(Answer, blank=True, null=True, related_name='answer_pages', on_delete=models.SET_NULL) redirect_to_page = models.ForeignKey( 'self', blank=True, null=True, on_delete=models.SET_NULL, related_name='redirect_to_pages', help_text="Choose another AnswerPage to redirect this page to") featured = models.BooleanField( default=False, help_text=("Check to make this one of two featured answers " "on the landing page.")) featured_rank = models.IntegerField(blank=True, null=True) category = models.ManyToManyField( 'Category', blank=True, help_text=("Categorize this answer. " "Avoid putting into more than one category.")) search_tags = models.CharField( max_length=1000, blank=True, help_text="Search words or phrases, separated by commas") related_resource = models.ForeignKey(RelatedResource, blank=True, null=True, on_delete=models.SET_NULL) related_questions = ParentalManyToManyField( 'self', symmetrical=False, blank=True, related_name='related_question', help_text='Maximum of 3 related questions') portal_topic = ParentalManyToManyField( PortalTopic, blank=True, help_text='Limit to 1 portal topic if possible') primary_portal_topic = ParentalKey( PortalTopic, blank=True, null=True, on_delete=models.SET_NULL, related_name='primary_portal_topic', help_text=("Use only if assigning more than one portal topic, " "to control which topic is used as a breadcrumb.")) portal_category = ParentalManyToManyField(PortalCategory, blank=True) user_feedback = StreamField([ ('feedback', v1_blocks.Feedback()), ], blank=True) share_and_print = models.BooleanField( default=False, help_text="Include share and print buttons above answer.") content_panels = CFGOVPage.content_panels + [ MultiFieldPanel([ FieldPanel('last_edited'), FieldPanel('question'), FieldPanel('statement'), FieldPanel('short_answer') ], heading="Page content", classname="collapsible"), FieldPanel('share_and_print'), StreamFieldPanel('answer_content'), MultiFieldPanel([ SnippetChooserPanel('related_resource'), AutocompletePanel('related_questions', target_model='ask_cfpb.AnswerPage') ], heading="Related resources", classname="collapsible"), MultiFieldPanel([ FieldPanel('portal_topic', widget=forms.CheckboxSelectMultiple), FieldPanel('primary_portal_topic'), FieldPanel('portal_category', widget=forms.CheckboxSelectMultiple) ], heading="Portal tags", classname="collapsible"), MultiFieldPanel([FieldPanel('featured')], heading="Featured answer on Ask landing page", classname="collapsible"), MultiFieldPanel([ AutocompletePanel('redirect_to_page', target_model='ask_cfpb.AnswerPage') ], heading="Redirect to another answer", classname="collapsible"), MultiFieldPanel([StreamFieldPanel('user_feedback')], heading="User feedback", classname="collapsible collapsed"), ] 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_content'), index.SearchField('short_answer') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(sidebar_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'ask-cfpb/answer-page.html' objects = CFGOVPageManager() def get_sibling_url(self): if self.answer_base: if self.language == 'es': sibling = self.answer_base.english_page else: sibling = self.answer_base.spanish_page if sibling and sibling.live and not sibling.redirect_to_page: return sibling.url def get_context(self, request, *args, **kwargs): portal_topic = self.primary_portal_topic or self.portal_topic.first() context = super(AnswerPage, self).get_context(request) context['related_questions'] = self.related_questions.all() context['description'] = (self.short_answer if self.short_answer else truncate_preview(self.answer_content)) context['last_edited'] = self.last_edited context['portal_page'] = get_portal_or_portal_search_page( portal_topic, language=self.language) context['breadcrumb_items'] = get_ask_breadcrumbs( language=self.language, portal_topic=portal_topic, ) context['about_us'] = get_standard_text(self.language, 'about_us') context['disclaimer'] = get_standard_text(self.language, 'disclaimer') context['sibling_url'] = self.get_sibling_url() return context def answer_content_text(self): raw_text = extract_raw_text(self.answer_content.stream_data) return strip_tags(" ".join([self.short_answer, raw_text])) def answer_content_data(self): return truncate_preview(self.answer_content_text()) def short_answer_data(self): return ' '.join( RichTextField.get_searchable_content(self, self.short_answer)) def text(self): short_answer = self.short_answer_data() answer_text = self.answer_content_text() full_text = f"{short_answer}\n\n{answer_text}\n\n{self.question}" return full_text def __str__(self): if self.answer_base: return f"{self.answer_base.id}: {self.title}" else: return self.title @property def clean_search_tags(self): return [tag.strip() for tag in self.search_tags.split(",")] @property def status_string(self): if self.redirect_to_page: 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.social_sharing_image: return self.social_sharing_image if not self.category.exists(): return None return self.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
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
all_articles = CoronaArticlePage.objects.all() if all_articles.count <= 4: return all_articles if self.tags.length == 0: return all_articles CoronaArticlePage.content_panels = [ MultiFieldPanel([ FieldPanel('title'), FieldPanel('author'), ImageChooserPanel('cover_image'), ], heading='Title & intro'), MultiFieldPanel([StreamFieldPanel('content')], heading='Main content'), MultiFieldPanel([StreamFieldPanel('sidebar')], heading='Sidebar content'), MultiFieldPanel([StreamFieldPanel('fullwidth')], heading='Bottom fullwidth content'), MultiFieldPanel([FieldPanel('related_title')], heading='Related articles'), MultiFieldPanel([FieldPanel('tags')], heading='Tags') ] CoronaArticlePage.search_fields = Page.search_fields + [ index.SearchField('tags') ] CoronaArticlePage.parent_page_types = ['home.CoronaIndexPage'] CoronaArticlePage.subpage_types = []
class Report(RoutablePageMixin, Post): """ Report class that inherits from the abstract Post model and creates pages for Policy Papers. """ parent_page_types = ['ReportsHomepage'] subpage_types = [] sections = StreamField( [('section', ReportSectionBlock(template="components/report_section_body.html", required=False))], null=True, blank=True) abstract = RichTextField(blank=True, null=True) acknowledgements = RichTextField(blank=True, null=True) source_word_doc = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Source Word Document') report_pdf = models.ForeignKey('wagtaildocs.Document', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name='Report PDF') dataviz_src = models.CharField(blank=True, null=True, max_length=300, help_text="") overwrite_sections_on_save = models.BooleanField( default=False, help_text= 'If checked, sections and endnote fields ⚠ will be overwritten ⚠ with Word document source on save. Use with CAUTION!' ) generate_pdf_on_publish = models.BooleanField( 'Generate PDF on save', default=False, help_text= '⚠ Save latest content before checking this ⚠\nIf checked, the "Report PDF" field will be filled with a generated pdf. Otherwise, leave this unchecked and upload a pdf to the "Report PDF" field.' ) revising = False featured_sections = StreamField([ ('featured', FeaturedReportSectionBlock(required=False, null=True)), ], null=True, blank=True) endnotes = StreamField([ ('endnote', EndnoteBlock(required=False, null=True)), ], null=True, blank=True) report_url = StreamField([ ('report_url', URLBlock(required=False, null=True)), ], null=True, blank=True) attachment = StreamField([ ('attachment', DocumentChooserBlock(required=False, null=True)), ], null=True, blank=True) partner_logo = models.ForeignKey('home.CustomImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') content_panels = [ MultiFieldPanel([ FieldPanel('title'), FieldPanel('subheading'), FieldPanel('date'), ImageChooserPanel('story_image'), ]), InlinePanel('authors', label=("Authors")), InlinePanel('programs', label=("Programs")), InlinePanel('subprograms', label=("Subprograms")), InlinePanel('topics', label=("Topics")), InlinePanel('location', label=("Locations")), MultiFieldPanel([ FieldPanel('abstract'), StreamFieldPanel('featured_sections'), FieldPanel('acknowledgements'), ]) ] sections_panels = [StreamFieldPanel('sections')] endnote_panels = [StreamFieldPanel('endnotes')] settings_panels = Post.settings_panels + [FieldPanel('dataviz_src')] promote_panels = Page.promote_panels + [ FieldPanel('story_excerpt'), ] pdf_panels = [ MultiFieldPanel([ DocumentChooserPanel('source_word_doc'), FieldPanel('overwrite_sections_on_save'), ], heading='Word Doc Import'), MultiFieldPanel([ FieldPanel('generate_pdf_on_publish'), DocumentChooserPanel('report_pdf'), StreamFieldPanel('attachment') ], heading='PDF Generation'), ImageChooserPanel('partner_logo') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading="Landing"), ObjectList(sections_panels, heading="Sections"), ObjectList(endnote_panels, heading="Endnotes"), ObjectList(promote_panels, heading="Promote"), ObjectList(settings_panels, heading='Settings', classname="settings"), ObjectList(pdf_panels, heading="PDF Publishing") ]) search_fields = Post.search_fields + [index.SearchField('sections')] def get_context(self, request): context = super().get_context(request) if getattr(request, 'is_preview', False): import newamericadotorg.api.report revision = PageRevision.objects.filter( page=self).last().as_page_object() report_data = newamericadotorg.api.report.serializers.ReportDetailSerializer( revision).data context['initial_state'] = json.dumps(report_data) return context def save(self, *args, **kwargs): super(Report, self).save(*args, **kwargs) if not self.overwrite_sections_on_save and not self.generate_pdf_on_publish: self.revising = False if not self.revising and self.source_word_doc is not None and self.overwrite_sections_on_save: self.revising = True parse_pdf(self) self.overwrite_sections_on_save = False self.save_revision() if not self.revising and self.generate_pdf_on_publish: generate_pdf.apply_async(args=(self.id, )) # Extra views @route(r'pdf/$') def pdf(self, request): if not self.report_pdf: return self.pdf_render(request) url = 'https://s3.amazonaws.com/newamericadotorg/' + self.report_pdf.file.name return redirect(url) @route(r'pdf/render/$') def pdf_render(self, request): response = HttpResponse(content_type='application/pdf;') response[ 'Content-Disposition'] = 'inline; filename=%s.pdf' % self.title response['Content-Transfer-Encoding'] = 'binary' protocol = 'https://' if request.is_secure() else 'http://' base_url = protocol + request.get_host() contents = generate_report_contents(self) authors = get_report_authors(self) html = loader.get_template('report/pdf.html').render({ 'page': self, 'contents': contents, 'authors': authors }) pdf = write_pdf(response, html, base_url) return response @route(r'print/$') def print(self, request): contents = generate_report_contents(self) authors = get_report_authors(self) return render(request, 'report/pdf.html', context={ 'page': self, 'contents': contents, 'authors': authors }) @route(r'[a-zA-Z0-9_\.\-]*/$') def section(self, request): # Serve the whole report, subsection routing is handled by React return self.serve(request) class Meta: verbose_name = 'Report'
class MagazineArticle(Page): teaser = RichTextField(blank=True) body = StreamField( [ ("heading", blocks.CharBlock(classname="full title")), ( "paragraph", blocks.RichTextBlock( features=[ "h2", "h3", "h4", "bold", "italic", "ol", "ul", "hr", "link", "document-link", "image", "superscript", "superscript", "strikethrough", "blockquote", ] ), ), ("image", ImageChooserBlock()), ("pullquote", blocks.BlockQuoteBlock()), ] ) body_migrated = models.TextField( help_text="Used only for content from old Drupal website.", null=True, blank=True, ) department = models.ForeignKey( MagazineDepartment, null=True, blank=True, on_delete=models.SET_NULL, related_name="articles", ) tags = ClusterTaggableManager(through=MagazineArticleTag, blank=True) search_template = "search/magazine_article.html" search_fields = Page.search_fields + [index.SearchField("body")] content_panels = Page.content_panels + [ FieldPanel("teaser", classname="full"), StreamFieldPanel("body", classname="full"), FieldPanel("body_migrated", classname="full"), InlinePanel( "authors", heading="Authors", help_text="Select one or more authors, who contributed to this article", ), MultiFieldPanel( [ PageChooserPanel("department", "magazine.MagazineDepartment"), FieldPanel("tags"), ], heading="Article information", ), ] parent_page_types = ["MagazineIssue"] subpage_types = [] def get_sitemap_urls(self): return [ { "location": self.full_url, "lastmod": self.latest_revision_created_at, "priority": 1, } ] def is_public_access(self): """ Check whether article should be accessible to all readers or only subscribers based on issue publication date. """ parent_issue = self.get_parent() today = arrow.utcnow() six_months_ago = today.shift(months=-6).date() # Issues older than six months are public access return parent_issue.specific.publication_date <= six_months_ago
class EntryPage(Entry, Page): # Search search_fields = Page.search_fields + [ index.SearchField('body'), index.SearchField('excerpt'), index.FilterField('page_ptr_id') ] # Panels content_panels = getattr(Entry, 'content_panels', []) promote_panels = Page.promote_panels + getattr(Entry, 'promote_panels', []) settings_panels = Page.settings_panels + [ FieldPanel('date'), FieldPanel('owner'), ] + getattr(Entry, 'settings_panels', []) # Parent and child settings parent_page_types = ['puput.BlogPage'] subpage_types = [] def get_sitemap_urls(self, request=None): from .urls import get_entry_url root_url = self.get_url_parts()[1] entry_url = get_entry_url(self, self.blog_page.page_ptr, root_url) return [{ 'location': root_url + entry_url, # fall back on latest_revision_created_at if last_published_at is null # (for backwards compatibility from before last_published_at was added) 'lastmod': (self.last_published_at or self.latest_revision_created_at), }] @property def blog_page(self): return self.get_parent().specific @property def related(self): return [ related.entrypage_to for related in self.related_entrypage_from.all() ] @property def has_related(self): return self.related_entrypage_from.count() > 0 def get_absolute_url(self): return self.full_url def get_context(self, request, *args, **kwargs): context = super(EntryPage, self).get_context(request, *args, **kwargs) context['blog_page'] = self.blog_page return context class Meta: verbose_name = _('Entry') verbose_name_plural = _('Entries')
class AbstractEmbedVideo(index.Indexed, models.Model): title = models.CharField(max_length=255, verbose_name=_('Title')) url = EmbedVideoField() thumbnail = models.ForeignKey(image_model_name, verbose_name="Thumbnail", null=True, blank=True, on_delete=models.SET_NULL, related_name='+') created_at = models.DateTimeField(auto_now_add=True) uploaded_by_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, editable=False, on_delete=models.SET_NULL) tags = TaggableManager(help_text=None, blank=True, verbose_name=_('Tags')) objects = EmbedVideoQuerySet.as_manager() def get_usage(self): return get_object_usage(self) @property def usage_url(self): return reverse('wagtail_embed_videos_video_usage', args=(self.id, )) search_fields = [ index.SearchField('title', partial_match=True, boost=10), index.RelatedFields('tags', [ index.SearchField('name', partial_match=True, boost=10), ]), index.FilterField('uploaded_by_user'), ] def __str__(self): return self.title def __init__(self, *args, **kwargs): super(AbstractEmbedVideo, self).__init__(*args, **kwargs) if args: if args[3] is None: create_thumbnail(self) def save(self, *args, **kwargs): super(AbstractEmbedVideo, self).save(*args, **kwargs) if not self.thumbnail: create_thumbnail(self) @property def default_alt_text(self): return self.title def is_editable_by_user(self, user): if user.has_perm('wagtail_embed_videos.change_embedvideo'): # user has global permission to change videos return True elif user.has_perm('wagtail_embed_videos.add_embedvideo') and\ self.uploaded_by_user == user: # user has video add permission, which also implicitly provides # permission to edit their own videos return True else: return False class Meta: abstract = True
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 AbstractDocument(CollectionMember, index.Indexed, models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) file = models.FileField(upload_to='documents', verbose_name=_('file')) created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True) uploaded_by_user = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('uploaded by user'), null=True, blank=True, editable=False, on_delete=models.SET_NULL ) tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags')) file_size = models.PositiveIntegerField(null=True, editable=False) # A SHA-1 hash of the file contents file_hash = models.CharField(max_length=40, blank=True, editable=False) objects = DocumentQuerySet.as_manager() search_fields = CollectionMember.search_fields + [ index.SearchField('title', partial_match=True, boost=10), index.AutocompleteField('title'), index.FilterField('title'), index.RelatedFields('tags', [ index.SearchField('name', partial_match=True, boost=10), index.AutocompleteField('name'), ]), index.FilterField('uploaded_by_user'), ] def is_stored_locally(self): """ Returns True if the image is hosted on the local filesystem """ try: self.file.path return True except NotImplementedError: return False @contextmanager def open_file(self): # Open file if it is closed close_file = False f = self.file if f.closed: # Reopen the file if self.is_stored_locally(): f.open('rb') else: # Some external storage backends don't allow reopening # the file. Get a fresh file instance. #1397 storage = self._meta.get_field('file').storage f = storage.open(f.name, 'rb') close_file = True # Seek to beginning f.seek(0) try: yield f finally: if close_file: f.close() def get_file_size(self): if self.file_size is None: try: self.file_size = self.file.size except Exception: # File doesn't exist return self.save(update_fields=['file_size']) return self.file_size def _set_file_hash(self, file_contents): self.file_hash = hashlib.sha1(file_contents).hexdigest() def get_file_hash(self): if self.file_hash == '': with self.open_file() as f: self._set_file_hash(f.read()) self.save(update_fields=['file_hash']) return self.file_hash def __str__(self): return self.title @property def filename(self): return os.path.basename(self.file.name) @property def file_extension(self): return os.path.splitext(self.filename)[1][1:] @property def url(self): return reverse('wagtaildocs_serve', args=[self.id, self.filename]) def get_usage(self): return get_object_usage(self) @property def usage_url(self): return reverse('wagtaildocs:document_usage', args=(self.id,)) def is_editable_by_user(self, user): from wagtail.documents.permissions import permission_policy return permission_policy.user_has_permission_for_instance(user, 'change', self) class Meta: abstract = True verbose_name = _('document') verbose_name_plural = _('documents')
class BrowsePage(CFGOVPage): header = StreamField([ ('text_introduction', molecules.TextIntroduction()), ('featured_content', organisms.FeaturedContent()), ], blank=True) content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('info_unit_group', organisms.InfoUnitGroup()), ('expandable_group', organisms.ExpandableGroup()), ('expandable', organisms.Expandable()), ('well', organisms.Well()), ('video_player', organisms.VideoPlayer()), ('snippet_list', organisms.ResourceList()), ('table_block', organisms.AtomicTableBlock( table_options={'renderer': 'html'} )), ('feedback', v1_blocks.Feedback()), ('raw_html_block', blocks.RawHTMLBlock( label='Raw HTML block' )), ('conference_registration_form', ConferenceRegistrationForm()), ('chart_block', organisms.ChartBlock()), ('mortgage_chart_block', organisms.MortgageChartBlock()), ('mortgage_map_block', organisms.MortgageMapBlock()), ('mortgage_downloads_block', MortgageDataDownloads()), ('data_snapshot', organisms.DataSnapshot()), ('job_listing_table', JobListingTable()), ('bureau_structure', organisms.BureauStructure()), ('yes_checklist', YESChecklist()), ], blank=True) secondary_nav_exclude_sibling_pages = models.BooleanField(default=False) # General content tab content_panels = CFGOVPage.content_panels + [ StreamFieldPanel('header'), StreamFieldPanel('content'), ] sidefoot_panels = CFGOVPage.sidefoot_panels + [ FieldPanel('secondary_nav_exclude_sibling_pages'), ] # Tab handler interface edit_handler = TabbedInterface([ ObjectList(content_panels, heading='General Content'), ObjectList(sidefoot_panels, heading='Sidebar'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) template = 'browse-basic/index.html' objects = PageManager() search_fields = CFGOVPage.search_fields + [ index.SearchField('content'), index.SearchField('header') ] @property def page_js(self): return ( super(BrowsePage, self).page_js + ['secondary-navigation.js'] ) def get_context(self, request, *args, **kwargs): context = super(BrowsePage, self).get_context(request, *args, **kwargs) context.update({ 'get_secondary_nav_items': get_secondary_nav_items }) return context
class ConferenceSubPage(PublicBasePage): """ Subpages for conferences. These inherit most of their template "goodness" from parent ConferencePage. """ body = StreamField(DefaultBodyFields()) content_panels = Page.content_panels + [ StreamFieldPanel('body'), ] + PublicBasePage.content_panels subpage_types = ['conferences.ConferenceSubPage'] search_fields = PublicBasePage.search_fields + [ index.SearchField('body'), ] api_fields = [ APIField('body'), ] @property def has_right_sidebar(self): """ Override default test to see if a right sidebar should be displayed. Returns: Boolean """ parent = self.get_parent_of_type('conference page') return parent.has_right_sidebar @property def has_social_media(self): """ Override default test for social media. Returns: Boolean """ parent = self.get_parent_of_type('conference page') return parent.has_social_media # Context def get_context(self, request): context = super(ConferenceSubPage, self).get_context(request) current_site = Site.find_for_request(request) parent = self.get_parent_of_type('conference page') # Set social media fields dynamically and # get all the values from the parent page. # This doesn't seem like a good practice # How else can this be done? social_media_fields = [ f.name for f in SocialMediaFields._meta.get_fields() ] for field in social_media_fields: exec('self.' + field + ' = ' + 'parent.' + field) context['primary_branding_color'] = parent.primary_branding_color context['secondary_branding_color'] = parent.secondary_branding_color context['conference_logo'] = parent.conference_logo context['conference_title'] = parent.title context['has_social_media'] = parent.has_social_media context['main_registration'] = parent.main_registration.all() context['sponsors'] = parent.sponsors.all() context['organizers'] = parent.organizers.all() context['secondary_registration'] = parent.sub_registration.all() context['secondary_registration_heading' ] = parent.secondary_registration_heading context['secondary_registration_description' ] = parent.secondary_registration_description context['home'] = parent.relative_url(current_site) return context
class EventCalendar(RoutablePageMixin, Page): """ Base calendar class which actually displays the calendar. """ heading = models.CharField(max_length=65) subheading = models.CharField(max_length=65) description = RichTextField(blank=True, help_text=_('Description of the calendar'), verbose_name=_('Description')) """ Short description of the calendar.""" default_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_('Default Image'), help_text=_('Default image to be used for calendar entries') ) """ Default image to be used for calendar entries """ content_panels = Page.content_panels + [ FieldPanel('description'), ImageChooserPanel('default_image'), FieldPanel('heading', classname="full"), FieldPanel('subheading', classname="full"), ] subpage_types = ['events.EventCalPage'] search_fields = Page.search_fields + [ index.SearchField('description') ] class Meta: verbose_name = _("Calendar Root Page") parent_page_types = ["home.TeamPage"] @route(r"^events/$") def getEvents(self, request: django.http.HttpRequest) -> django.http.HttpResponse: """ Route that returns the events. Is accessed by fullcalender as the api to call :param request: Django request :return: JSON of the events ard their details """ def filterForPeriod(request: django.http.HttpRequest) -> models.QuerySet: """ Filter for the specific time frame being queried by FullCalendar :param request: Django request :return: Queryset of EventCalPage objects """ # TODO make sure start and end points work properly events = EventCalPage.objects.filter(start_dt__range=[request.GET['start'], request.GET['end']]).live() return events if request.is_ajax(): result = [ { 'title': event.title, 'start': event.start_dt.astimezone(pytz.timezone(request.GET['timezone'])).isoformat(), 'end': event.end_dt.astimezone(pytz.timezone(request.GET['timezone'])).isoformat(), 'url': event.url } for event in filterForPeriod(request) ] json_output = json.dumps(result) return HttpResponse(json_output) else: return super(EventCalendar, self).serve(request) @route(r"^category/(?P<category>[\w\-]+)/$") def viewByCategory(self, request: django.http.HttpRequest, **kwargs) -> django.http.HttpResponse: """ View calendar by a specific category :param request: Django request :param kwargs: Django request kwargs :return: HttpResponse that shows a calendar filtered by a category """ return render(request, "events/event_calendar_category.html", {'self': self, 'page': self, 'category': kwargs['category']}) @route(r"^category/(?P<category>[\w\-]+)/events/$") def getEventsByCategory(self, request: django.http.HttpRequest, **kwargs: dict) -> django.http.HttpResponse: """ Gets the events for a specific category for a specific timeframe. Is accessed by fullcalender.js :param request: Django request :param kwargs: Django request kwargs :return: HttpResponse """ categ = kwargs['category'] def filterForPeriod(request: django.http.HttpRequest, categ: str) -> django.http.HttpResponse: """ Filters for a period taking into account the specific category :param request: :param categ: :return: """ # TODO make sure start and end points work properly events = EventCalPage.objects.filter(start_dt__range=[request.GET['start'], request.GET['end']]).live() events = events.filter(categories__name__iexact=categ) return events if request.is_ajax(): result = [ { 'title': event.title, 'start': event.start_dt.astimezone(pytz.timezone(request.GET['timezone'])).isoformat(), 'end': event.end_dt.astimezone(pytz.timezone(request.GET['timezone'])).isoformat(), 'url': event.url } for event in filterForPeriod(request, categ) ] json_output = json.dumps(result) return HttpResponse(json_output) else: return render(request, "events/event_calendar_category.html", {'self': self, 'page': self, 'category': kwargs['category']}) @route(r'^ical/$') def icalView(self, request: django.http.HttpRequest, *args, **kwargs: dict) -> django.http.HttpResponse: """ Route that produces the ical files requested by clients. :param request: Django request :param args: Django request args :param kwargs: Django request kwargs :return: HttpResponse containing an ical file """ cal = Calendar() cal.add('prodid', '-//Calendar Event event//mxm.dk//') cal.add('version', '2.0') for entry in EventCalPage.objects.live(): event = Event() event.add('summary', entry.title) event.add('dtstart', entry.start_dt) event.add('dtend', entry.end_dt) event.add('dtstamp', timezone.now()) event.add('uid', str(entry.pk)) event['location'] = vText(entry.location) cal.add_component(event) return HttpResponse(cal.to_ical(), content_type="text/calendar") @route(r"^category/(?P<category>[\w\-]+)/ical/$") def icalViewCategory(self, request: django.http.HttpRequest, *args, **kwargs: dict) -> django.http.HttpResponse: """ Route that produces the ical files requested by clients, but filtered for a specific category :param request: Django HttpRequest :param args: Django request args :param kwargs: Django request kwargs :return: HttpResponse containing an ical file """ cal = Calendar() cal.add('prodid', '-//Calendar Event event//mxm.dk//') cal.add('version', '2.0') print(kwargs['category']) for entry in EventCalPage.objects.filter(categories__name__iexact=kwargs['category']).live(): event = Event() event.add('summary', entry.title) event.add('dtstart', entry.start_dt) event.add('dtend', entry.end_dt) event.add('dtstamp', timezone.now()) event.add('uid', str(entry.pk)) event['location'] = vText(entry.location) cal.add_component(event) return HttpResponse(cal.to_ical(), content_type="text/calendar") @property def get_categories(self) -> models.QuerySet: """ Gets the calendar categories that currently exist :return: Returns a Queryset of Category objects """ return Category.objects.all() @property def get_url(self) -> str: """ Gets the url of the calendar page :return: Url of the calendar page """ return self.url def get_context(self, request): context = super().get_context(request) next_event = EventCalPage.objects.filter(start_dt__gte=timezone.now()).order_by('start_dt').first() context['nextevent'] = next_event return context
class JobOfferIndexPage(BasePage): subpage_types = ["JobOfferPage"] max_count_per_parent = 1 heading = models.CharField("Überschrift", max_length=255, blank=True) highlight_in_heading = models.CharField( "Hervorhebungen in der Überschrift", help_text="Wiederhole Text aus der Überschrift der farblich hervorgehoben werden soll", blank=True, max_length=255, ) subtitle = models.CharField("Untertitel", max_length=255, blank=True) before_jobs = StreamField( StandardStreamBlock, blank=True, verbose_name="Intro-Text (wenn Jobs vorhanden)", help_text="Wird als Text vor der Liste der Stellenanzeigen angezeigt. Aber nur wenn es auch Stellenanzeigen gibt.", ) after_jobs = StreamField( StandardStreamBlock, blank=True, verbose_name="Outro-Text (wenn Jobs vorhanden)", help_text="Wird als Text nach der Liste der Stellenanzeigen angezeigt. Aber nur wenn es auch Stellenanzeigen gibt.", ) empty = StreamField( StandardStreamBlock(), blank=True, null=True, verbose_name="Wenn keine Jobs", help_text="Wird angezeigt, wenn es keine Stellenanzeigen gibt.", ) def get_context(self, request): context = super().get_context(request) context["jobs"] = JobOfferPage.objects.all().live() return context search_fields = BasePage.search_fields + [ index.SearchField("heading"), index.SearchField("subtitle"), index.SearchField("before_jobs"), index.SearchField("after_jobs"), ] content_panels = [ MultiFieldPanel( [ FieldPanel("title"), FieldPanel("heading"), FieldPanel("highlight_in_heading"), FieldPanel("subtitle"), ], "Kopf", ), StreamFieldPanel("before_jobs"), HelpPanel( template="jobs/admin_add_job_button.html", heading="Stellenauschreibung erstellen", ), StreamFieldPanel("after_jobs"), StreamFieldPanel("empty"), ] class Meta: verbose_name = "Auflistung von Stellenausschreibungen" verbose_name_plural = "Auflistungen von Stellenausschreibungen"
class EventCalPage(RoutablePageMixin, Page): """ Calendar entry/ an even base model. """ categories = models.ManyToManyField('events.Category', through='events.CategoryEventPage', blank=True, help_text=_('Categories this event belongs to'), verbose_name=_('Categories')) """Optional category that a specific calendar entry may belong to""" instruments = models.ManyToManyField('Instrument', through='events.instrumentEventPage', blank=True, help_text=_('Instruments this event belongs to'), verbose_name=_('Instruments')) description = RichTextField(blank=True, help_text=_('Description of event'), verbose_name=_('Description')) """Required. Description of the event/calendar entry""" image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_('Image'), ) """Optional image to associate with a calendar entry. Only really useful for the website""" start_dt = models.DateTimeField(help_text=_('Starting time of event'), verbose_name=_('Start of Event')) """Required. Start datetime of the event/calender entry""" end_dt = models.DateTimeField(help_text=_('End time of event. Does not need to be same day.'), verbose_name=_('End of Event')) """Required. End datetime of the event/calender entry. Must be after start_dt else it raises a Validation Error""" location = models.CharField(max_length=255, blank=True, help_text=_('Location of event'), verbose_name=_('Location')) """Optional location information""" omap = PlainLocationField(based_fields=['location'], zoom=7) problem_status = models.BooleanField(default=False, help_text=_('Whether there is a problem with the event'), verbose_name=_('Problem Status')) """Optional true/false indicating whether there is an issue with an event. It is important to both the ical files and the website""" problem_text = models.TextField(blank=True, null=True, help_text=_('Text that describes the problem. Keep brief.'), verbose_name=_('Problem Description')) """Optional text that describes what is wrong. Used in conjunction with problem_status. Requires problem_status = true to work at all.""" content_panels = Page.content_panels + [ FieldPanel('description'), ImageChooserPanel('image'), FieldPanel('start_dt'), FieldPanel('end_dt'), FieldPanel('location'), FieldPanel('omap'), MultiFieldPanel([ InlinePanel("event_categories", label=_("Categories")) ]), MultiFieldPanel([ InlinePanel("event_instruments", label=_("Instruments")) ]), ] settings_panels = Page.settings_panels + [ FieldPanel('problem_status'), FieldPanel('problem_text') ] parent_page_types = ["events.EventCalendar"] search_fields = Page.search_fields + [ index.SearchField('description'), index.RelatedFields('categories', [ index.SearchField('name'), index.SearchField('description'), ]) ] class Meta: verbose_name = _("Calendar Event") def __str__(self): return self.title def clean(self): """ Checks that the end date and time occurs after the start date and date """ if self.start_dt > self.end_dt: raise ValidationError(_('Start date and time must be before end date and time')) def save(self, *args, **kwargs): """ Overloads the save method of the Page model. It applies the default image to a calendar entry/event if it doesn't already have one. """ if not self.image: self.image = EventCalendar.objects.live()[0].default_image super(EventCalPage, self).save() def get_context(self, request): context = super().get_context(request) # Add extra variables and return the updated context instrumentpresences = Instrument.objects.annotate(num_participations=Count('instrumentparticipations',filter=Q(instrumentparticipations__event_page=self.pk, instrumentparticipations__choice='OUI'))) presences = Participation.objects.filter(event_page=self.pk, choice='OUI') absences = Participation.objects.filter(event_page=self.pk, choice='NON') questions = Participation.objects.filter(event_page=self.pk, choice='PEUT-ETRE') instrumentmaxs = InstrumentEventPage.objects.filter(page=self.pk) context['instrumentpresences'] = instrumentpresences context['presences'] = presences context['absences'] = absences context['questions'] = questions context['instrumentmaxs'] = instrumentmaxs return context @property def get_categories(self) -> models.QuerySet: """ Gets all the event categories. :return: Queryset containing the Categories objects """ return Category.objects.filter(eventcalpage=self.pk) @property def get_status_text(self) -> Union[str, bool]: """ Shows the status text of a calender entry/event :return: Str if the event is finished, or begun but not yet completed else false. """ if self.end_dt < timezone.now(): return _("Event Finished") elif self.problem_status: return self.problem_text elif self.start_dt < timezone.now() < self.end_dt: return _("Event has begun") # TODO better time format @ {self.start_dt.isoformat()} else: return False @route(r'^ical/$') def icalView(self, request: django.http.HttpRequest, *args, **kwargs: dict) -> django.http.HttpResponse: """ Route that returns an ical file for a specific event. :param request: Django HttpRequest :param args: Normal request args :param kwargs: Normal request kwargs :return: ical file as part of HttpResponse with only the details of a specific event """ cal = Calendar() cal.add('prodid', '-//Calendar Event event//mxm.dk//') cal.add('version', '2.0') event = Event() event.add('summary', self.title) event.add('dtstart', self.start_dt) event.add('dtend', self.end_dt) event.add('dtstamp', timezone.now()) event.add('uid', str(self.pk)) event['location'] = vText(self.location) cal.add_component(event) return HttpResponse(cal.to_ical(), content_type="text/calendar")
class AbstractDocument(CollectionMember, index.Indexed, models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) file = models.FileField(upload_to='documents', verbose_name=_('file')) created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True) uploaded_by_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('uploaded by user'), null=True, blank=True, editable=False, on_delete=models.SET_NULL) tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags')) file_size = models.PositiveIntegerField(null=True, editable=False) # A SHA-1 hash of the file contents file_hash = models.CharField(max_length=40, blank=True, editable=False) objects = DocumentQuerySet.as_manager() search_fields = CollectionMember.search_fields + [ index.SearchField('title', partial_match=True, boost=10), index.AutocompleteField('title'), index.FilterField('title'), index.RelatedFields('tags', [ index.SearchField('name', partial_match=True, boost=10), index.AutocompleteField('name'), ]), index.FilterField('uploaded_by_user'), ] def clean(self): """ Checks for WAGTAILDOCS_EXTENSIONS and validates the uploaded file based on allowed extensions that were specified. Warning : This doesn't always ensure that the uploaded file is valid as files can be renamed to have an extension no matter what data they contain. More info : https://docs.djangoproject.com/en/3.1/ref/validators/#fileextensionvalidator """ allowed_extensions = getattr(settings, "WAGTAILDOCS_EXTENSIONS", None) if allowed_extensions: validate = FileExtensionValidator(allowed_extensions) validate(self.file) def is_stored_locally(self): """ Returns True if the image is hosted on the local filesystem """ try: self.file.path return True except NotImplementedError: return False @contextmanager def open_file(self): # Open file if it is closed close_file = False f = self.file if f.closed: # Reopen the file if self.is_stored_locally(): f.open('rb') else: # Some external storage backends don't allow reopening # the file. Get a fresh file instance. #1397 storage = self._meta.get_field('file').storage f = storage.open(f.name, 'rb') close_file = True # Seek to beginning f.seek(0) try: yield f finally: if close_file: f.close() def get_file_size(self): if self.file_size is None: try: self.file_size = self.file.size except Exception: # File doesn't exist return self.save(update_fields=['file_size']) return self.file_size def _set_file_hash(self, file_contents): self.file_hash = hashlib.sha1(file_contents).hexdigest() def get_file_hash(self): if self.file_hash == '': with self.open_file() as f: self._set_file_hash(f.read()) self.save(update_fields=['file_hash']) return self.file_hash def __str__(self): return self.title @property def filename(self): return os.path.basename(self.file.name) @property def file_extension(self): return os.path.splitext(self.filename)[1][1:] @property def url(self): if getattr(settings, 'WAGTAILDOCS_SERVE_METHOD', None) == 'direct': try: return self.file.url except NotImplementedError: # backend does not provide a url, so fall back on the serve view pass return reverse('wagtaildocs_serve', args=[self.id, self.filename]) def get_usage(self): return get_object_usage(self) @property def usage_url(self): return reverse('wagtaildocs:document_usage', args=(self.id, )) def is_editable_by_user(self, user): from wagtail.documents.permissions import permission_policy return permission_policy.user_has_permission_for_instance( user, 'change', self) @property def content_type(self): content_types_lookup = getattr(settings, 'WAGTAILDOCS_CONTENT_TYPES', {}) return (content_types_lookup.get(self.file_extension.lower()) or guess_type(self.filename)[0] or 'application/octet-stream') @property def content_disposition(self): inline_content_types = getattr(settings, 'WAGTAILDOCS_INLINE_CONTENT_TYPES', ['application/pdf']) if self.content_type in inline_content_types: return 'inline' else: return "attachment; filename={0}; filename*=UTF-8''{0}".format( urllib.parse.quote(self.filename)) class Meta: abstract = True verbose_name = _('document') verbose_name_plural = _('documents')
class AbstractImage(ImageFileMixin, CollectionMember, index.Indexed, models.Model): title = models.CharField(max_length=255, verbose_name=_('title')) file = models.ImageField(verbose_name=_('file'), upload_to=get_upload_to, width_field='width', height_field='height') width = models.IntegerField(verbose_name=_('width'), editable=False) height = models.IntegerField(verbose_name=_('height'), editable=False) created_at = models.DateTimeField(verbose_name=_('created at'), auto_now_add=True, db_index=True) uploaded_by_user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('uploaded by user'), null=True, blank=True, editable=False, on_delete=models.SET_NULL) tags = TaggableManager(help_text=None, blank=True, verbose_name=_('tags')) focal_point_x = models.PositiveIntegerField(null=True, blank=True) focal_point_y = models.PositiveIntegerField(null=True, blank=True) focal_point_width = models.PositiveIntegerField(null=True, blank=True) focal_point_height = models.PositiveIntegerField(null=True, blank=True) file_size = models.PositiveIntegerField(null=True, editable=False) # A SHA-1 hash of the file contents file_hash = models.CharField(max_length=40, blank=True, editable=False) objects = ImageQuerySet.as_manager() def _set_file_hash(self, file_contents): self.file_hash = hashlib.sha1(file_contents).hexdigest() def get_file_hash(self): if self.file_hash == '': with self.open_file() as f: self._set_file_hash(f.read()) self.save(update_fields=['file_hash']) return self.file_hash def get_upload_to(self, filename): folder_name = 'original_images' filename = self.file.field.storage.get_valid_name(filename) # do a unidecode in the filename and then # replace non-ascii characters in filename with _ , to sidestep issues with filesystem encoding filename = "".join( (i if ord(i) < 128 else '_') for i in string_to_ascii(filename)) # Truncate filename so it fits in the 100 character limit # https://code.djangoproject.com/ticket/9893 full_path = os.path.join(folder_name, filename) if len(full_path) >= 95: chars_to_trim = len(full_path) - 94 prefix, extension = os.path.splitext(filename) filename = prefix[:-chars_to_trim] + extension full_path = os.path.join(folder_name, filename) return full_path def get_usage(self): return get_object_usage(self) @property def usage_url(self): return reverse('wagtailimages:image_usage', args=(self.id, )) search_fields = CollectionMember.search_fields + [ index.SearchField('title', partial_match=True, boost=10), index.AutocompleteField('title'), index.FilterField('title'), index.RelatedFields('tags', [ index.SearchField('name', partial_match=True, boost=10), index.AutocompleteField('name'), ]), index.FilterField('uploaded_by_user'), ] def __str__(self): return self.title def get_rect(self): return Rect(0, 0, self.width, self.height) def get_focal_point(self): if self.focal_point_x is not None and \ self.focal_point_y is not None and \ self.focal_point_width is not None and \ self.focal_point_height is not None: return Rect.from_point( self.focal_point_x, self.focal_point_y, self.focal_point_width, self.focal_point_height, ) def has_focal_point(self): return self.get_focal_point() is not None def set_focal_point(self, rect): if rect is not None: self.focal_point_x = rect.centroid_x self.focal_point_y = rect.centroid_y self.focal_point_width = rect.width self.focal_point_height = rect.height else: self.focal_point_x = None self.focal_point_y = None self.focal_point_width = None self.focal_point_height = None def get_suggested_focal_point(self): with self.get_willow_image() as willow: faces = willow.detect_faces() if faces: # Create a bounding box around all faces left = min(face[0] for face in faces) top = min(face[1] for face in faces) right = max(face[2] for face in faces) bottom = max(face[3] for face in faces) focal_point = Rect(left, top, right, bottom) else: features = willow.detect_features() if features: # Create a bounding box around all features left = min(feature[0] for feature in features) top = min(feature[1] for feature in features) right = max(feature[0] for feature in features) bottom = max(feature[1] for feature in features) focal_point = Rect(left, top, right, bottom) else: return None # Add 20% to width and height and give it a minimum size x, y = focal_point.centroid width, height = focal_point.size width *= 1.20 height *= 1.20 width = max(width, 100) height = max(height, 100) return Rect.from_point(x, y, width, height) @classmethod def get_rendition_model(cls): """ Get the Rendition model for this Image model """ return cls.renditions.rel.related_model def get_rendition(self, filter): if isinstance(filter, str): filter = Filter(spec=filter) cache_key = filter.get_cache_key(self) Rendition = self.get_rendition_model() try: rendition_caching = True cache = caches['renditions'] rendition_cache_key = Rendition.construct_cache_key( self.id, cache_key, filter.spec) cached_rendition = cache.get(rendition_cache_key) if cached_rendition: return cached_rendition except InvalidCacheBackendError: rendition_caching = False try: rendition = self.renditions.get( filter_spec=filter.spec, focal_point_key=cache_key, ) except Rendition.DoesNotExist: # Generate the rendition image try: logger.debug( "Generating '%s' rendition for image %d", filter.spec, self.pk, ) start_time = time.time() generated_image = filter.run(self, BytesIO()) logger.debug("Generated '%s' rendition for image %d in %.1fms", filter.spec, self.pk, (time.time() - start_time) * 1000) except: # noqa:B901,E722 logger.debug("Failed to generate '%s' rendition for image %d", filter.spec, self.pk) raise # Generate filename input_filename = os.path.basename(self.file.name) input_filename_without_extension, input_extension = os.path.splitext( input_filename) # A mapping of image formats to extensions FORMAT_EXTENSIONS = { 'jpeg': '.jpg', 'png': '.png', 'gif': '.gif', 'webp': '.webp', } output_extension = filter.spec.replace( '|', '.') + FORMAT_EXTENSIONS[generated_image.format_name] if cache_key: output_extension = cache_key + '.' + output_extension # Truncate filename to prevent it going over 60 chars output_filename_without_extension = input_filename_without_extension[:( 59 - len(output_extension))] output_filename = output_filename_without_extension + '.' + output_extension rendition, created = self.renditions.get_or_create( filter_spec=filter.spec, focal_point_key=cache_key, defaults={ 'file': File(generated_image.f, name=output_filename) }) if rendition_caching: cache.set(rendition_cache_key, rendition) return rendition def is_portrait(self): return (self.width < self.height) def is_landscape(self): return (self.height < self.width) @property def filename(self): return os.path.basename(self.file.name) @property def default_alt_text(self): # by default the alt text field (used in rich text insertion) is populated # from the title. Subclasses might provide a separate alt field, and # override this return self.title def is_editable_by_user(self, user): from wagtail.images.permissions import permission_policy return permission_policy.user_has_permission_for_instance( user, 'change', self) class Meta: abstract = True
class ProductProxy(index.Indexed, get_model("catalogue", "Product")): def popularity(self): months_to_run = settings.OSCAR_SEARCH.get( "MONTHS_TO_RUN_ANALYTICS", 3) orders_above_date = timezone.now() - relativedelta( months=months_to_run) Line = get_model("order", "Line") return Line.objects.filter( product=self, order__date_placed__gte=orders_above_date).count() def price(self): selector = get_class("partner.strategy", "Selector") strategy = selector().strategy() if self.is_parent: return strategy.fetch_for_parent(self).price.incl_tax return strategy.fetch_for_product(self).price.incl_tax def string_attrs(self): return [str(a.value_as_text) for a in self.attribute_values.all()] def attrs(self): values = self.attribute_values.all().select_related("attribute") result = {} for value in values: at = value.attribute if at.type == at.OPTION: result[value.attribute.code] = value.value.option elif at.type == at.MULTI_OPTION: result[value.attribute.code] = [ a.option for a in value.value ] elif es_type_for_product_attribute(at) != "text": result[value.attribute.code] = value.value if self.is_parent: for child in ProductProxy.objects.filter(parent=self): result = merge_dicts(result, child.attrs()) return result def object(self): "Mimic a haystack search result" return self def category_id(self): return self.categories.values_list("id", flat=True) def category_name(self): return list(self.categories.values_list("name", flat=True)) @classmethod def get_search_fields(cls): # hook extra_product_fields for overriding return process_product_fields(super().get_search_fields()) search_fields = [ index.FilterField("id"), index.SearchField("title", partial_match=True, boost=2), index.AutocompleteField("title"), index.AutocompleteField("upc", es_extra={"analyzer": "keyword"}), index.FilterField("upc"), index.SearchField("upc", boost=3, es_extra={"analyzer": "keyword"}), index.SearchField("description", partial_match=True), index.FilterField("popularity"), index.FilterField("price", es_extra={"type": "double"}), index.FilterField("category_id"), index.SearchField("category_name", partial_match=True), index.AutocompleteField("category_name"), index.RelatedFields( "categories", [ index.SearchField("description", partial_match=True), index.SearchField("slug"), index.SearchField("full_name"), index.SearchField("get_absolute_url"), ], ), index.RelatedFields( "stockrecords", [ index.FilterField("price_currency"), index.SearchField("partner_sku"), index.SearchField("price_excl_tax"), index.FilterField("partner"), index.FilterField("num_in_stock"), ], ), index.FilterField("parent_id"), index.FilterField("structure"), index.FilterField("is_standalone"), index.FilterField("slug"), index.FilterField("rating"), index.FilterField("date_created"), index.FilterField("date_updated"), index.SearchField("string_attrs"), index.FilterField("attrs", es_extra=product_attributes_es_config()), ] class Meta: proxy = True
class AbstractFlexPage(Page): # commented image_block because of https://luminousweb.atlassian.net/browse/YOUG-16 # for now it is not necessary and creates misunderstanding with image_content_block # commented accordion_block, person_block & RTE because of https://luminousweb.atlassian.net/browse/YOUG-234 # as a duplicate module to accordion_list_block and not used person_block and Full RTE content = StreamField( [ # ('accordion_block', blocks.AccordionBlock()), ('accordion_list_block', blocks.AccordionListBlock()), ('advisor_analyst_block', blocks.AdvisorsBlock()), ('turquoise_block', blocks.TurquoiseListBlocks()), ('callout_module_block', blocks.CalloutsModuleBlock()), ('three_column_callout_module_block', blocks.ThreeColumnCalloutsModuleBlock()), ('contact_block', blocks.ContactInfoBlock()), ('download_block', blocks.DownloadBlock()), ('icon_block', blocks.IconsListBlock()), ('iframe_block', blocks.IframeBlock()), ('image_content_block', blocks.ImageContentBlock()), ('image_full_bleed_block', blocks.ImageFullBleedBlock()), # ('image_person_block', blocks.ImagePersonBlock()), ('image_people_block', blocks.ImagePeopleBlock()), ('newsfeed_block', blocks.NewsFeedModuleBlock()), ('number_block', blocks.NumberingListBlock()), ('event_module_block', blocks.EventListBlock()), ('form_module_block', blocks.FormModuleBlock()), ("full_rte_editor", blocks.RichTextBlockFull()), ('hero_banner_block', blocks.HeroBannerBlock()), ('padding_block', blocks.PaddingBlock()), ("quotation_block", blocks.QuotationBlock()), ('table_block', blocks.TableModuleBlock()), ('timeline_block', blocks.TimeLineModuleBlock()), ("title_and_text", blocks.TitleAndTextBlock()), ('two_columns_block', blocks.TwoColumnModuleBlock()), ("video_block", blocks.VideoBlock()), ('widget_block', blocks.WidgetChooserBlock()), ('right_widget_block', blocks.RightWidgetChooserBlock()), # ("full_rich_text", blocks.RichTextBlock()), ("rss_block", blocks.RssBlock()), ("rss_news_block", blocks.RssNewsBlock()), ("back_page_block", blocks.BackPageLinkBlock()), ("Logos_block", blocks.LogosListBlock()), # ('image_block', blocks.ImageBlock()), ], null=True, blank=True, ) subtitle = models.CharField(max_length=100, null=True, blank=True) is_timestamp_displayed = models.BooleanField( default=False, blank=True, null=True, verbose_name='Display published date') content_panels = Page.content_panels + [ FieldPanel("subtitle"), FieldPanel("is_timestamp_displayed"), StreamFieldPanel("content") ] search_fields = Page.search_fields + [index.SearchField('content')] class Meta: abstract = True
class EnforcementActionPage(AbstractFilterPage): public_enforcement_action = models.CharField(max_length=150, blank=True) initial_filing_date = models.DateField(null=True, blank=True) settled_or_contested_at_filing = models.CharField(max_length=10, choices=[('Settled', 'Settled'), ('Contested', 'Contested')], blank=True) court = models.CharField(default='', max_length=150, blank=True) content = StreamField([ ('full_width_text', organisms.FullWidthText()), ('expandable', organisms.Expandable()), ('expandable_group', organisms.ExpandableGroup()), ('notification', molecules.Notification()), ('table_block', organisms.AtomicTableBlock(table_options={'renderer': 'html'})), ('feedback', v1_blocks.Feedback()), ], blank=True) content_panels = [StreamFieldPanel('header'), StreamFieldPanel('content')] metadata_panels = [ FieldPanel('public_enforcement_action'), FieldPanel('initial_filing_date'), InlinePanel('defendant_types', label='Defendant/Respondent Type'), InlinePanel('categories', label="Forum", min_num=1, max_num=2), FieldPanel('court'), InlinePanel('docket_numbers', label="Docket Number", min_num=1), FieldPanel('settled_or_contested_at_filing'), InlinePanel('statuses', label="Status", min_num=1), InlinePanel('products', label="Products"), InlinePanel('at_risk_groups', label="At Risk Groups"), InlinePanel('statutes', label="Statutes/Regulations"), InlinePanel('enforcement_dispositions', label='Final Disposition'), ] settings_panels = [ MultiFieldPanel(CFGOVPage.promote_panels, 'Settings'), MultiFieldPanel([ FieldPanel('preview_title'), FieldPanel('preview_subheading'), FieldPanel('preview_description'), FieldPanel('secondary_link_url'), FieldPanel('secondary_link_text'), ImageChooserPanel('preview_image'), ], heading='Page Preview Fields', classname='collapsible'), FieldPanel('authors', 'Authors'), MultiFieldPanel([ FieldPanel('date_published'), FieldPanel('comments_close_by'), ], 'Relevant Dates', classname='collapsible'), MultiFieldPanel(Page.settings_panels, 'Scheduled Publishing'), FieldPanel('language', 'Language'), MultiFieldPanel(CFGOVPage.archive_panels, 'Archive'), ] edit_handler = TabbedInterface([ ObjectList(AbstractFilterPage.content_panels + content_panels, heading='General Content'), ObjectList(metadata_panels, heading='Metadata'), ObjectList(CFGOVPage.sidefoot_panels, heading='Sidebar'), ObjectList(settings_panels, heading='Configuration') ]) template = 'enforcement-action/index.html' objects = PageManager() search_fields = AbstractFilterPage.search_fields + [ index.SearchField('content') ] def get_context(self, request): context = super(EnforcementActionPage, self).get_context(request) dispositions = self.enforcement_dispositions.all() context.update({ 'total_consumer_relief': sum(disp.final_order_consumer_redress + disp.final_order_other_consumer_relief for disp in dispositions), 'total_cmp': sum(disp.final_order_civil_money_penalty for disp in dispositions), 'defendant_types': [ d.get_defendant_type_display() for d in self.defendant_types.all() ], 'statutes': [s.statute for s in self.statutes.all()], 'products': [p.product for p in self.products.all()], 'at_risk_groups': [g.at_risk_group for g in self.at_risk_groups.all()] }) return context
class FormPage(AbstractEmailForm): landing_page_template = 'pages/form_page_confirmation.html' subpage_types = [] top_content = StreamField(BASE_BLOCKS + COLUMNS_BLOCKS, blank=True) confirmation_text = models.TextField( default='Your registration was submitted') send_confirmation_email = models.BooleanField( default=False, verbose_name='Send confirmation email?') confirmation_email_subject = models.CharField( default='ISIMIP Form submission confirmation.', max_length=500, verbose_name='Email subject', null=True, blank=True) confirmation_email_text = models.TextField( default= 'The form was submitted successfully. We will get back to you soon.', verbose_name='Email text', null=True, blank=True) bottom_content = StreamField(BASE_BLOCKS + COLUMNS_BLOCKS, blank=True) button_name = models.CharField(max_length=500, verbose_name='Button name', default='Submit') content_panels = AbstractEmailForm.content_panels + [ StreamFieldPanel('top_content'), StreamFieldPanel('bottom_content') ] form_content_panels = [ InlinePanel('form_fields', label="Form fields"), FieldPanel('button_name'), FieldPanel('confirmation_text', classname="full"), MultiFieldPanel([ FieldPanel('send_confirmation_email'), FieldPanel('confirmation_email_subject'), FieldPanel('confirmation_email_text', classname="full"), ], "Confirmation email"), MultiFieldPanel([ FieldPanel('to_address', classname="full"), FieldPanel('from_address', classname="full"), FieldPanel('subject', classname="full"), ], "Email"), ] search_fields = Page.search_fields + [ index.SearchField('top_content'), index.SearchField('bottom_content'), ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(form_content_panels, heading='Form Builder'), ObjectList(AbstractEmailForm.promote_panels, heading='Promote'), ObjectList(AbstractEmailForm.settings_panels, heading='Settings', classname="settings"), ]) def get_context(self, request, *args, **kwargs): context = super().get_context(request, *args, **kwargs) message = {'tags': 'success', 'text': self.confirmation_text} context['confirmation_messages'] = [message] return context 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 correct and resubmit.' 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): self.get_submission_class().objects.create(form_data=json.dumps( form.cleaned_data, cls=DjangoJSONEncoder), page=self) 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 for field in form: if isinstance(field.field.widget, EmailInput): confirmation_email_address = field.value() break if confirmation_email_address: extra_content = [ '', ] for field in form: 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, )