class Performance(Page): """ A Performance page is a child of Concert pages. Its used to model performances of individual works within a Concert. These will publish as a list on the parent concert page. """ base_form_class = PerformanceAdminForm composition = models.ForeignKey('Composition', null=False, blank=False, on_delete=models.PROTECT, related_name='+') supplemental_text = RichTextField(blank=True, features=['bold', 'italic']) conductor = models.ForeignKey('Person', null=True, blank=True, on_delete=models.PROTECT, related_name='+') performance_date = ParentalManyToManyField('ConcertDate', blank=True) def full_clean(self, *args, **kwargs): self.title = str(self.composition) # Clear sluge and let Wagtail resolve it self.slug = '' super().full_clean(*args, **kwargs) def clean(self): self.title = str(self.composition) super().clean() # Route requests to these pages to their parent def get_url_parts(self, *args, **kwargs): return self.get_parent().specific.get_url_parts(*args, **kwargs) content_panels = [ AutocompletePanel('composition', target_model='main.Composition'), FieldPanel('supplemental_text'), InlinePanel('performer', label='Performers'), AutocompletePanel('conductor', target_model='main.Person'), FieldPanel( 'performance_date', widget=forms.CheckboxSelectMultiple, ) ] promote_panels = [] parent_page_types = ['Concert'] subpage_types = []
def test_target_models_nonexistent_type(self): autocomplete_panel = AutocompletePanel( 'owner', target_model='testapp.hous', ).bind_to(model=House) with self.assertRaises(ImproperlyConfigured): autocomplete_panel.target_model
def test_render_multiple_as_field(self): edit_handler = (ObjectList([AutocompletePanel('occupants') ]).bind_to(model=House, request=self.request)) form_class = edit_handler.get_form_class() form = form_class(instance=self.test_house) autocomplete_panel = edit_handler.children[0].bind_to( instance=self.test_house, form=form, request=self.request) result = autocomplete_panel.render_as_field() self.assertIn('Occupants', result) soup = BeautifulSoup(result, 'html5lib') element = soup.find(attrs={'data-autocomplete-input': True}) self.assertIsNotNone(element) self.assertEqual(element['data-autocomplete-input-name'], 'occupants') self.assertJSONEqual(element['data-autocomplete-input-value'], [{ 'pk': self.house_occupant.pk, 'title': self.house_occupant.name, }]) self.assertNotIn('data-autocomplete-input-can-create', element.attrs) self.assertNotIn('data-autocomplete-input-is-single', element.attrs) # test the conversion of the form field's value from datadict field_value = element['data-autocomplete-input-value'] form = form_class({'occupants': field_value}, instance=self.test_house) self.assertTrue(form.is_valid())
class Composition(index.Indexed, models.Model): # Note: calling unescape on the title below is only ok because the input is # being sanitized by the RichTextField. title = RichTextField(features=['bold', 'italic']) composer = models.ForeignKey('Person', null=True, blank=False, on_delete=models.SET_NULL, related_name='+') def __str__(self): return unescape(strip_tags(self.title)) def display_title(self): return str(self) def autocomplete_label(self): return "{} - {}".format(unescape(strip_tags(self.title)), self.composer) panels = [ FieldPanel('title'), AutocompletePanel('composer', target_model='main.Person') ] search_fields = [ index.SearchField('title', partial_match=True), index.RelatedFields('composer', [ index.SearchField('first_name', partial_match=True), index.SearchField('last_name', partial_match=True), ]), ]
class ActionResponsibleParty(OrderedModel): action = ParentalKey(Action, on_delete=models.CASCADE, related_name='responsible_parties', verbose_name=_('action')) organization = models.ForeignKey( Organization, on_delete=models.CASCADE, related_name='responsible_actions', limit_choices_to=Q(dissolution_date=None), verbose_name=_('organization'), ) panels = [ AutocompletePanel('organization'), ] class Meta: ordering = ['action', 'order'] index_together = (('action', 'order'), ) unique_together = (('action', 'organization'), ) verbose_name = _('action responsible party') verbose_name_plural = _('action responsible parties') def __str__(self): return str(self.organization)
def setUp(self): self.request = RequestFactory().get('/') user = AnonymousUser() self.request.user = user model = House # a model with a foreign key to Person # a AutocompletePanel class that works on House's 'owner' field self.edit_handler = (ObjectList([AutocompletePanel('owner') ]).bind_to(model=House, request=self.request)) self.base_autocomplete_panel = self.edit_handler.children[0] # build a form class containing the fields that AutocompletePanel wants self.form_class = self.edit_handler.get_form_class() # a test instance of House with an owner and an occupant self.house_owner = Person.objects.create(name="An owner") self.house_occupant = Person.objects.create(name="An occupant") self.test_house = model.objects.create(owner=self.house_owner, occupants=[self.house_occupant]) self.form = self.form_class(instance=self.test_house) self.autocomplete_panel = self.base_autocomplete_panel.bind_to( instance=self.test_house, form=self.form)
class Subscription(ClusterableModel): RECURRING_STATUS_CHOICES = [ (STATUS_ACTIVE, _(STATUS_ACTIVE.capitalize())), (STATUS_PROCESSING, _(STATUS_PROCESSING.capitalize())), (STATUS_PAUSED, _(STATUS_PAUSED.capitalize())), (STATUS_CANCELLED, _(STATUS_CANCELLED.capitalize())), (STATUS_INACTIVE, _(STATUS_INACTIVE.capitalize())), ] user = models.ForeignKey('newstream_user.User', on_delete=models.SET_NULL, null=True) gateway = models.ForeignKey('site_settings.PaymentGateway', on_delete=models.SET_NULL, null=True) is_test = models.BooleanField(default=False) profile_id = models.CharField(max_length=191, unique=True) recurring_amount = models.DecimalField(max_digits=20, decimal_places=2) currency = models.CharField(max_length=20) recurring_status = models.CharField(max_length=255, choices=RECURRING_STATUS_CHOICES, default=STATUS_INACTIVE, blank=True, null=True) subscribe_date = models.DateTimeField() created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey('newstream_user.User', related_name='subscription_created_by', on_delete=models.SET_NULL, blank=True, null=True) linked_user_deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False) panels = [ AutocompletePanel('user', heading=_('User')), FieldPanel('gateway', heading=_('Payment Gateway')), FieldPanel('is_test', heading=_('Is Test Subscription?')), FieldPanel('profile_id', heading=_('Profile ID')), FieldPanel('recurring_amount', heading=_('Recurring Amount')), FieldPanel('currency', heading=_('Currency')), FieldPanel('recurring_status', heading=_('Recurring Status')), FieldPanel('subscribe_date', heading=_('Subscribe Date')), FieldPanel('linked_user_deleted', heading=_('Donor User Deleted?')), ] def __str__(self): return '#' + str(self.id) + ' - ' + self.profile_id def isRecurringCancelled(self): return True if self.recurring_status == STATUS_CANCELLED else False def isRecurringProcessing(self): return True if self.recurring_status == STATUS_PROCESSING else False class Meta: ordering = ['-subscribe_date', '-created_at'] verbose_name = _('Subscription') verbose_name_plural = _('Subscriptions')
class HomePage(Page): parent_page_type = ['wagtailcore.Page'] subpage_types = [ 'blog.BlogIndexPage', 'contact.ContactPage', ] banner_title = models.CharField(max_length=250, blank=False, null=True) banner_subtitle = RichTextField(features=['bold', 'italic']) banner_image = models.ForeignKey( "wagtailimages.Image", null=True, blank=False, on_delete=models.SET_NULL, related_name="+", ) banner_cta = models.ForeignKey( "wagtailcore.Page", null=True, blank=False, on_delete=models.SET_NULL, related_name="+", ) body = RichTextField(blank=True) api_fields = [ APIField('banner_title'), APIField('banner_image'), APIField('banner_cta'), APIField('banner_subtitle') ] content_panels = Page.content_panels + [ FieldPanel('banner_title'), FieldPanel('banner_subtitle'), ImageChooserPanel('banner_image'), AutocompletePanel('banner_cta'), FieldPanel('body', classname='full'), MultiFieldPanel([InlinePanel("carousel_images", max_num=5, min_num=1)], heading='carousel images') ] custom_panels = [ FieldPanel('banner_title'), FieldPanel('banner_subtitle'), ImageChooserPanel('banner_image'), ] # override edit_handler # edit_handler = TabbedInterface([ # ObjectList(content_panels, heading='Content'), # ObjectList(Page.promote_panels, heading='Promote'), # ObjectList(Page.settings_panels, heading='Settings'), # ObjectList(custom_panels, heading='Sidebar Settings') # ]) def get_context(self, request): context = super().get_context(request) blog_indexes = self.get_children().live().order_by( '-first_published_at') context['blog_indexes'] = blog_indexes return context
class HostConnection(Orderable): event = ParentalKey("home.Event", on_delete=models.CASCADE, related_name="host_connections") speaker = models.ForeignKey( "home.Speaker", on_delete=models.CASCADE, null=True, blank=False, related_name="host_connections", ) panels = [AutocompletePanel("speaker")]
class BlogPageComment(Orderable): page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='comments') posted = models.DateTimeField(default=timezone.now) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='blog_page_comments') name = models.CharField(blank=True, max_length=250) email = models.EmailField(blank=True, max_length=250) comment = models.TextField(blank=True, max_length=1250) panels = [ FieldPanel('name'), FieldPanel('email'), AutocompletePanel('user', target_model=settings.AUTH_USER_MODEL), FieldPanel('posted'), FieldPanel('comment'), ]
def test_render_create_as_field(self): edit_handler = (ObjectList([AutocompletePanel('group') ]).bind_to(model=Person, request=self.request)) form_class = edit_handler.get_form_class() form = form_class(instance=self.house_occupant) autocomplete_panel = edit_handler.children[0].bind_to( instance=self.house_occupant, form=form, request=self.request) result = autocomplete_panel.render_as_field() self.assertIn('Group', result) soup = BeautifulSoup(result, 'html5lib') element = soup.find(attrs={'data-autocomplete-input': True}) self.assertIsNotNone(element) self.assertEqual(element['data-autocomplete-input-name'], 'group') self.assertJSONEqual(element['data-autocomplete-input-value'], 'null') self.assertIn('data-autocomplete-input-can-create', element.attrs) self.assertIn('data-autocomplete-input-is-single', element.attrs)
class MovieReview(BlogDetailPage): template = "blog/post.html" subpage_types = [] parent_page_type = [ "blog.BlogListingPage", ] movie = models.ForeignKey( "movies.Movie", blank=False, null=True, on_delete=models.SET_NULL, ) content_panels = Page.content_panels + [ SnippetChooserPanel("author"), AutocompletePanel("movie"), StreamFieldPanel("content"), ]
class Campaign(ClusterableModel): title = I18nCharField(max_length=255) from_address = models.EmailField() recipients = models.ManyToManyField(TargetGroup) template = models.ForeignKey(EmailTemplate, on_delete=models.SET_NULL, null=True) sent = models.BooleanField(default=False, editable=False) sent_at = models.DateTimeField(blank=True, null=True) panels = [ FieldPanel('title', heading=_('Title')), FieldPanel('from_address', heading=_('From Address')), FieldPanel('template', heading=_('Template')), AutocompletePanel('recipients', heading=_('Recipients')), ] def __str__(self): return str(self.title) class Meta: ordering = ['-id'] verbose_name = _('Campaign') verbose_name_plural = _('Campaigns')
class Site(ClusterableModel): name = models.CharField('Name', max_length=255, unique=True) slug = models.SlugField('Slug', unique=True, editable=False, allow_unicode=True) domain = models.CharField( 'Domain Name', max_length=255, unique=True, help_text='Specify the domain name without the scheme, ' 'e.g. "example.com" instead of "https://example.com"') twitter_handle = models.CharField( 'Twitter Handle', blank=True, max_length=16, help_text='Specify the twitter handle starting with "@"' ) added = models.DateTimeField(auto_now_add=True) objects = models.Manager() scanned = ScannedSitesManager() regions = ParentalManyToManyField( 'Region', blank=True, related_name='sites', help_text='Select which leaderboard you would like this ' 'news site to appear on' ) panels = [ FieldPanel('name'), FieldPanel('domain'), FieldPanel('twitter_handle'), AutocompletePanel('regions', target_model='sites.Region'), ] class Meta: ordering = ['name'] def __str__(self): return self.name def clean(self): self.slug = slugify(self.name, allow_unicode=True) if len(self.slug) == 0: raise ValidationError('Slug must not be an empty string') def save(self, *args, **kwargs): # Calling full_clean in save ensures that the slug will be # autogenerated and validated no matter what route the data # took to get into the database. # https://code.djangoproject.com/ticket/13100 self.full_clean() super(Site, self).save(*args, **kwargs) def to_dict(self): """Generate a JSON-serializable dict of this object's attributes, including the results of the most recent scan.""" # TODO optimize this (denormalize latest scan into Site?) return dict( name=self.name, domain=self.domain, absolute_url=self.get_absolute_url(), **self.scans.latest().to_dict() ) def get_absolute_url(self): return reverse('sites:site', kwargs={'slug': self.slug})
class AnswerPage(CFGOVPage): """Page type for Ask CFPB answers.""" from ask_cfpb.models import Answer last_edited = models.DateField( blank=True, null=True, help_text="Change the date to today if you make a significant change.") question = models.TextField(blank=True) statement = models.TextField( blank=True, help_text=( "(Optional) Use this field to rephrase the question title as " "a statement. Use only if this answer has been chosen to appear " "on a money topic portal (e.g. /consumer-tools/debt-collection).")) 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) content_panels = CFGOVPage.content_panels + [ MultiFieldPanel([ FieldPanel('last_edited'), FieldPanel('question'), FieldPanel('statement'), FieldPanel('short_answer') ], heading="Page content", classname="collapsible"), StreamFieldPanel('answer_content'), MultiFieldPanel([ SnippetChooserPanel('related_resource'), AutocompletePanel('related_questions', page_type='ask_cfpb.AnswerPage', is_single=False) ], 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', page_type='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 Truncator(self.answer_content).words( 40, truncate=' ...')) 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 __str__(self): if self.answer_base: return '{}: {}'.format(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 SingleAutocompletePage(Page): target = models.ForeignKey(TargetPage, on_delete=models.PROTECT) content_panels = Page.content_panels + [ AutocompletePanel('target', page_type='testapp.TargetPage'), ]
class Event(FixUrlMixin, Page): event_id = models.IntegerField(unique=True, null=True, blank=True, default=None) title_en = models.CharField( verbose_name=_("title"), max_length=255, blank=True, help_text=_( "The page title as you'd like it to be seen by the public"), ) title_translated = TranslatedField("title", "title_en") short_overview_sk = models.CharField( max_length=255, blank=True, ) short_overview_en = models.CharField( max_length=255, blank=True, ) short_overview = TranslatedField("short_overview_sk", "short_overview_en") description_sk = RichTextField(blank=True) description_en = RichTextField(blank=True) description = TranslatedField("description_sk", "description_en") date_and_time = models.DateTimeField(default=timezone.now, verbose_name=_("date and time")) location = models.ForeignKey( "home.Location", null=True, blank=True, on_delete=models.SET_NULL, ) video_url = models.URLField( null=True, blank=True, help_text=format_html( _("Supports Youtube, Vimeo and {}{}{}"), mark_safe( "<a href='https://github.com/wagtail/wagtail/blob/master/" "wagtail/embeds/oembed_providers.py' target='_blank'>"), _("other websites"), mark_safe("</a>"), ), ) ticket_url = models.URLField(null=True, blank=True) category = models.ForeignKey( "home.Category", null=True, blank=True, on_delete=models.SET_NULL, ) icon = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) show_on_festivalpage = models.BooleanField(default=False) wordpress_url = models.CharField(max_length=255, unique=True, null=True, blank=True) related_festival = models.ForeignKey( "home.FestivalPage", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) content_panels_sk = Page.content_panels + [ MultiFieldPanel([ FieldPanel("date_and_time"), AutocompletePanel("location"), FieldPanel("category"), PageChooserPanel("related_festival", "home.FestivalPage"), ImageChooserPanel("icon"), ]), MultiFieldPanel( [ FieldPanel("short_overview_sk"), FieldPanel("description_sk"), FieldPanel("video_url"), FieldPanel("ticket_url"), ], heading=_("description"), ), InlinePanel("speaker_connections", heading="speakers"), InlinePanel("host_connections", heading="hosts"), ] content_panels_en = [ FieldPanel("title_en", classname="full title"), MultiFieldPanel( [FieldPanel("short_overview_en"), FieldPanel("description_en")], heading=_("description"), ), ] promote_panels = Page.promote_panels + [ FieldPanel("show_on_festivalpage"), ] edit_handler = TabbedInterface([ ObjectList(content_panels_sk, heading="Content SK"), ObjectList(content_panels_en, heading="Content EN"), ObjectList(promote_panels, heading="Promote"), ObjectList(Page.settings_panels, heading="Settings", classname="settings"), ]) parent_page_types = ["home.EventIndexPage"] subpage_types = [] class Meta: verbose_name = _("event") verbose_name_plural = _("events") def get_url_parts(self, request=None): """Insert PK of object to url""" site_id, root_url, page_path = super().get_url_parts(request) page_path = page_path.split("/") page_path.insert(-2, str(self.event_id)) return site_id, root_url, "/".join(page_path) def get_context(self, request, *args, **kwargs): from home.models.pages import last_festival context = super().get_context(request, *args, **kwargs) context["header_festival"] = last_festival(self) context["today"] = timezone.now().date() return context def save(self, *args, **kwargs): if self.event_id is None: last_event_id = (Event.objects.aggregate( Max("event_id"))["event_id__max"] or 0) self.event_id = last_event_id + 1 return super().save(*args, **kwargs) @cached_property def speakers_limited(self): connections = list(self.speaker_connections.all()) return { "under_limit": [c.speaker.title for c in connections[:3]], "over_limit_count": len(connections[3:]), "over_limit_names": ", ".join(c.speaker.title for c in connections[3:]), } def delete(self, *args, **kwargs): self.unpublish(user=kwargs.get("user"))
class DonationForm(ClusterableModel): AMOUNT_TYPE_CHOICES = [ ('fixed', _('Fixed Amount')), ('stepped', _('Fixed Steps')), ('custom', _('Custom Amount')), ('stepped_custom', _('Fixed Steps with Custom Amount Option')), ] DEFAULT_FREQ_CHOICES = [ ('onetime', _('One-time')), ('monthly', _('Monthly')), ] title = I18nCharField(max_length=191, unique=True) description = I18nTextField(blank=True) default_frequency = models.CharField(max_length=20, choices=DEFAULT_FREQ_CHOICES, blank=False, default='onetime') amount_type = models.CharField(max_length=20, choices=AMOUNT_TYPE_CHOICES) fixed_amount = models.DecimalField( blank=True, null=True, max_digits=20, decimal_places=2, help_text= _('Define fixed donation amount if you chose "Fixed Amount" for your Amount Type.' )) allowed_gateways = models.ManyToManyField('site_settings.PaymentGateway') donation_footer_text = I18nRichTextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) deleted = models.BooleanField(default=False) panels = [ FieldPanel('title', heading=_('Title')), FieldPanel('description', heading=_('Description')), FieldPanel('default_frequency', heading=_('Default Donation Frequency'), widget=RadioSelect), FieldPanel('amount_type', heading=_('Donation amount Type')), FieldPanel('fixed_amount', heading=_('Define Fixed Donation amount')), InlinePanel( 'amount_steps', label=_('Fixed Amount Steps'), heading=_('Define Fixed Donation amount Steps'), help_text= _('Define fixed donation amount steps if you chose "Fixed Steps" for your Amount Type.' )), AutocompletePanel('allowed_gateways', heading=_('Allowed Payment Gateways')), InlinePanel('donation_meta_fields', label=_('Donation Meta Fields'), heading=_('Donation Meta Fields')), FieldPanel( 'donation_footer_text', heading=_('Footer Text(Under Donation Details Form)'), help_text= _('Footer text to be displayed under the "Donation Details" Form (Step 2 of payment)' )) ] class Meta: ordering = ['title'] verbose_name = _('Donation Form') verbose_name_plural = _('Donation Forms') def __str__(self): return str(self.title) def isDefaultMonthly(self): return self.default_frequency == 'monthly' def isAmountFixed(self): return self.amount_type == 'fixed' def isAmountStepped(self): return self.amount_type == 'stepped' def isAmountCustom(self): return self.amount_type == 'custom' def isAmountSteppedCustom(self): return self.amount_type == 'stepped_custom'
class ArticlePage(RoutablePageMixin, Page): headline = RichTextField(features=["italic"]) subdeck = RichTextField(features=["italic"], null=True, blank=True) kicker = models.ForeignKey(Kicker, null=True, blank=True, on_delete=models.PROTECT) body = StreamField( [ ("paragraph", RichTextBlock()), ("photo", PhotoBlock()), ("photo_gallery", ListBlock(GalleryPhotoBlock(), icon="image")), ("embed", EmbeddedMediaBlock()), # ("form", FormBlock()), ], blank=True, ) summary = RichTextField( features=["italic"], null=True, blank=True, help_text= "Displayed on the home page or other places to provide a taste of what the article is about.", ) featured_image = models.ForeignKey( CustomImage, null=True, blank=True, on_delete=models.PROTECT, help_text="Shown at the top of the article and on the home page.", ) featured_caption = RichTextField(features=["italic"], blank=True, null=True) Forms = models.ForeignKey( FormField, null=True, blank=True, on_delete=models.SET_NULL, ) content_panels = [ MultiFieldPanel( [FieldPanel("headline", classname="title"), FieldPanel("subdeck")]), MultiFieldPanel( [ InlinePanel( "authors", panels=[ AutocompletePanel("author", target_model="core.Contributor") ], label="Author", ), AutocompletePanel("kicker", target_model="core.Kicker"), ImageChooserPanel("featured_image"), FieldPanel("featured_caption"), ], heading="Metadata", classname="collapsible", ), FieldPanel("summary"), StreamFieldPanel("body"), FieldPanel("Forms"), # PageChooserPanel('feedback_form_page', ['base.FormPage']), ] search_fields = Page.search_fields + [ index.SearchField("headline"), index.SearchField("subdeck"), index.SearchField("body"), index.SearchField("summary"), index.RelatedFields("kicker", [index.SearchField("title")]), index.SearchField("get_author_names", partial_match=True), ] subpage_types = [] def clean(self): super().clean() soup = BeautifulSoup(self.headline, "html.parser") self.title = soup.text @route(r"^$") def post_404(self, request): """Return an HTTP 404 whenever the page is accessed directly. This is because it should instead by accessed by its date-based path, i.e. `<year>/<month>/<slug>/`.""" raise Http404 def set_url_path(self, parent): """Make sure the page knows its own path. The published date might not be set, so we have to take that into account and ignore it if so.""" date = self.get_published_date() or timezone.now() self.url_path = f"{parent.url_path}{date.year}/{date.month:02d}/{self.slug}/" return self.url_path def serve_preview(self, request, mode_name): request.is_preview = True return self.serve(request) def get_context(self, request): context = super().get_context(request) context["authors"] = self.get_authors() return context def get_authors(self): return [r.author for r in self.authors.select_related("author")] def get_author_names(self): return [a.name for a in self.get_authors()] def get_published_date(self): return (self.go_live_at or self.first_published_at or getattr(self.get_latest_revision(), "created_at", None)) def get_text_html(self): """Get the HTML that represents paragraphs within the article as a string.""" builder = "" for block in self.body: if block.block_type == "paragraph": builder += str(block.value) return builder def get_plain_text(self): builder = "" soup = BeautifulSoup(self.get_text_html(), "html.parser") for para in soup.findAll("p"): builder += para.text builder += " " return builder[:-1] def get_related_articles(self): found_articles = [] related_articles = [] current_article_text = self.get_plain_text() if current_article_text is not None: current_article_words = set(current_article_text.split(" ")) authors = self.get_authors() for author in authors: articles = author.get_articles() for article in articles: if article.headline != self.headline: text_to_match = article.get_plain_text() article_words = set(text_to_match.split(" ")) found_articles.append(( article, len( list( current_article_words.intersection( article_words))), )) found_articles.sort(key=operator.itemgetter(1), reverse=True) for i in range(min(4, len(found_articles))): related_articles.append(found_articles[i][0]) return related_articles def get_first_chars(self, n=100): """Convert the body to HTML, extract the text, and then build a string out of it until we have at least n characters. If this isn't possible, then return None.""" text = self.get_plain_text() if len(text) < n: return None punctuation = {".", "!"} for i in range(n, len(text)): if text[i] in punctuation: if i + 1 == len(text): return text elif text[i + 1] == " ": return text[:i + 1] return None def get_meta_tags(self): tags = {} tags["og:type"] = "article" tags["og:title"] = self.title tags["og:url"] = self.full_url tags["og:site_name"] = self.get_site().site_name # description: either the article's summary or first paragraph if self.summary is not None: tags["og:description"] = self.summary tags["twitter:description"] = self.summary else: first_chars = self.get_first_chars() if first_chars is not None: tags["og:description"] = first_chars tags["twitter:description"] = first_chars # image if self.featured_image is not None: # pylint: disable=E1101 rendition = self.featured_image.get_rendition("min-1200x1200") rendition_url = self.get_site().root_url + rendition.url tags["og:image"] = rendition_url tags["twitter:image"] = rendition_url tags["twitter:site"] = "@rpipoly" tags["twitter:title"] = self.title if "twitter:description" in tags and "twitter:image" in tags: tags["twitter:card"] = "summary_large_image" else: tags["twitter:card"] = "summary" return tags
class AnswerPage(CFGOVPage): """ Page type for Ask CFPB answers. """ from ask_cfpb.models import Answer last_edited = models.DateField( blank=True, null=True, help_text="Change the date to today if you make a significant change.") question = models.TextField(blank=True) statement = models.TextField( blank=True, help_text=( "(Optional) Use this field to rephrase the question title as " "a statement. Use only if this answer has been chosen to appear " "on a money topic portal (e.g. /consumer-tools/debt-collection).")) short_answer = RichTextField(blank=True, help_text='Optional answer intro') answer = RichTextField( blank=True, features=[ 'bold', 'italic', 'h2', 'h3', 'h4', 'link', 'ol', 'ul', 'document-link', 'image', 'embed', 'ask-tips', 'edit-html' ], help_text=( "Do not use H2 or H3 to style text. Only use the HTML Editor " "for troubleshooting. To style tips, warnings and notes, " "select the content that will go inside the rule lines " "(so, title + paragraph) and click the Pencil button " "to style it. Re-select the content and click the button " "again to unstyle the tip.")) answer_base = models.ForeignKey(Answer, blank=True, null=True, related_name='answer_pages', on_delete=models.SET_NULL) redirect_to = models.ForeignKey( Answer, blank=True, null=True, on_delete=models.SET_NULL, related_name='redirected_pages', help_text="Choose another Answer to redirect this page to") 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.")) subcategory = models.ManyToManyField( 'SubCategory', blank=True, help_text=("Choose only subcategories that belong " "to one of the categories checked above.")) 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') answer_id = models.IntegerField(default=0) 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) content_panels = CFGOVPage.content_panels + [ MultiFieldPanel([ FieldPanel('last_edited'), FieldPanel('question'), FieldPanel('statement'), FieldPanel('short_answer'), FieldPanel('answer') ], heading="Page content", classname="collapsible"), MultiFieldPanel([ SnippetChooserPanel('related_resource'), AutocompletePanel('related_questions', page_type='ask_cfpb.AnswerPage', is_single=False) ], 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([ AutocompletePanel('redirect_to_page', page_type='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'), index.SearchField('short_answer') ] edit_handler = TabbedInterface([ ObjectList(content_panels, heading='Content'), ObjectList(sidebar_panels, heading='Sidebar (English only)'), ObjectList(CFGOVPage.settings_panels, heading='Configuration'), ]) objects = CFGOVPageManager() def get_context(self, request, *args, **kwargs): context = super(AnswerPage, self).get_context(request) context['related_questions'] = self.related_questions.all() context['description'] = self.short_answer if self.short_answer \ else Truncator(self.answer).words(40, truncate=' ...') context['answer_id'] = self.answer_base.id if self.language == 'es': context['search_tags'] = self.clean_search_tags context['tweet_text'] = Truncator(self.question).chars( 100, truncate=' ...') context['disclaimer'] = get_reusable_text_snippet( SPANISH_DISCLAIMER_SNIPPET_TITLE) context['category'] = self.category.first() elif self.language == 'en': context['about_us'] = get_reusable_text_snippet( ABOUT_US_SNIPPET_TITLE) context['disclaimer'] = get_reusable_text_snippet( ENGLISH_DISCLAIMER_SNIPPET_TITLE) context['last_edited'] = self.last_edited # breadcrumbs and/or category should reflect # the referrer if it is a consumer tools portal or # ask category page context['category'], context['breadcrumb_items'] = \ get_question_referrer_data( request, self.category.all()) subcategories = [] for subcat in self.subcategory.all(): if subcat.parent == context['category']: subcategories.append(subcat) for related in subcat.related_subcategories.all(): if related.parent == context['category']: subcategories.append(related) context['subcategories'] = set(subcategories) return context def get_template(self, request): printable = request.GET.get('print', False) if self.language == 'es': if printable == 'true': return 'ask-cfpb/answer-page-spanish-printable.html' return 'ask-cfpb/answer-page-spanish.html' return 'ask-cfpb/answer-page.html' def __str__(self): if self.answer_base: return '{}: {}'.format(self.answer_base.id, self.title) else: return self.title @property def 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 MlObjectPage(Page): disposer_id = models.ForeignKey('wagtailcore.Page', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', verbose_name="распорядитель объекта") install_date = models.DateField("дата установки", blank=True, null=True) status_ok = models.BooleanField("параметры в норме", default=True) intro = models.TextField(verbose_name='краткое описание', blank=True) description = RichTextField(blank=True, verbose_name='описание') tags = ClusterTaggableManager(through=MlObjectTag, blank=True, verbose_name='ключи поиска') auto_tags = ClusterTaggableManager(through=MlObjectAutoTag, blank=True, verbose_name='состояние', related_name='ml_obj_auto_tags',) # logical block # is system normally operating is_enabled = models.BooleanField(default=True, verbose_name='включен') # is critical for parent system is_critical = models.BooleanField(default=False, verbose_name='критически важен') # is object periodically diagnosed diagnosed = models.BooleanField(default=True, verbose_name='осмотрен') # have normal maintenance have_maintenance = models.BooleanField(default=True, verbose_name='обслужен') # object have cll-down to it's work call_down = models.BooleanField(default=False, verbose_name='есть замечания') # system working, but should be repaired have_to_be_repaired = models.BooleanField(default=False, verbose_name='требует ремонта') # if critical and critically broken, then parent system broken too is_critically_broken = models.BooleanField(default=False, verbose_name='сломан') # search block search_fields = Page.search_fields + [ index.SearchField('intro'), index.SearchField('description'), ] def btn_status_action_on(self, obj): pass def btn_status_action_off(self, obj): pass btn_status = MlButton( symbol_on="✔", symbol_off=chr(9940), name_on="статус Ok", name_off="есть сбои", state_field=status_ok, alert_on="success", alert_off="danger", position=1, btn_action_on=btn_status_action_on, btn_action_off=btn_status_action_off ) # logical block # status symbol description alert influence tag_dict = { 'status_ok': ("✔", "статус Ok", "success", 7, # ✔ # menu item (function name) item description influence {"status_ok_to_bad": ("изменить статус на 'есть сбои'", 10,), } ), 'status_bad': (chr(9940), "есть сбои", "danger", 25, # ⛔ {"status_bad_to_ok": ("изменить статус на 'ОК'", 10,), } ), 'is_enabled': ("💡", "включен", "success", 5, # 💡 {"do_disable": ("выключить", 10,), } ), 'is_disabled': ("🔌", "выключен", "dark", 21, # 🔌 {"do_enable": ("включить", 10,), } ), 'diagnosed': (chr(9730), "осмотрен", "success", 3), # ☂ 'have_to_be_diagnosed': (chr(9200), "пришло время осмотра", "info", 15, # ⏰ {"do_diagnose": ("выполнить осмотр", 10,), } ), 'need_service': (chr(9997), "пришло время TO", "info", 18, # ✍ {"do_service": ("выполнить ТО", 10,), } ), 'have_maintenance': (chr(9874), "прошел ТО", "success", 2), # ⚒ 'is_critical': (chr(9889), "критически важен", "success", 4), # ⚡ 'call_down': (chr(9785), "есть замечания", "warning", 20), # ☹ 'have_to_be_repaired': (chr(9888), "требует ремонта", "warning", 23), # ⚠ 'is_critically_broken': (chr(9760), "сломан", "danger", 28), # ☠ } # getters and setters @property def status_bad(self): return not self.status_ok @status_bad.setter def status_bad(self, value): self.status_ok = not value self.save() @property def is_disabled(self): return not self.is_enabled @is_disabled.setter def is_disabled(self, value): self.is_enabled = not value self.save() @property def have_to_be_diagnosed(self): return not self.diagnosed @have_to_be_diagnosed.setter def have_to_be_diagnosed(self, value): self.diagnosed = not value self.save() @property def need_service(self): return not self.have_maintenance @need_service.setter def need_service(self, value): self.have_maintenance = not value self.save() # functions from menu items def status_ok_to_bad(self): pass def status_bad_to_ok(self): pass def do_disable(self): self.is_enabled = False self.save() def do_enable(self): self.is_enabled = True self.save() def do_diagnose(self): pass def do_service(self): pass # FixMe def main_image(self): gallery_item = self.gallery_images.first() if gallery_item: return gallery_item.image else: return None # colored borders for objects def ml_obj_border(self): tags = self.auto_tags.all() border = "success" influence = 0 for tag in tags: for key in self.tag_dict: if self.tag_dict[key][0] == tag.name and influence < self.tag_dict[key][3]: influence = self.tag_dict[key][3] border = self.tag_dict[key][2] return border # override get_context method def get_context(self, request, *args, **kwargs): # check buttons ORM state on every page reload self.btn_status.get_orm_state(self) # Execute function by name. If name have '.', execute last part func_name = request.POST.get('func_name') execute_func(self, func_name) # execute main "get_context" function context = super().get_context(request) return context """ # override save method def save(self, *args, **kwargs): if self.is_critically_broken: self.have_to_be_repaired = True if self.have_to_be_repaired: self.status_ok = False self.auto_tags.clear() status_args = ('status_ok', 'status_bad', 'is_enabled', 'is_disabled', 'diagnosed', 'have_to_be_diagnosed', 'have_maintenance', 'need_service', 'is_critical', 'call_down', 'have_to_be_repaired', 'is_critically_broken') for status in status_args: if getattr(self, status): self.auto_tags.add(obj_tag_dict[status][0]) ################ if crit and is_crit: ancestors = reversed(self.get_ancestors()) for ancestor in ancestors: if ancestor.title != "root": with ancestor.specific.is_critically_broken as crit_brok: if crit_brok: is_crit = ancestor.specific.is_critical continue alarm = True with ancestor.specific.broken_if_all_elements_broken as brok: if brok: childr = ancestors.get_children().live() for child in childr: if (child.specific.is_critically_broken == False) and \ (child.specific.is_critical == True): alarm = False if is_crit: ancestor.specific.is_critically_broken = alarm is_crit = ancestor.specific.is_critical """ #TODO: change tags claster (self.tags.add('auto_alarm',"events")) #super(MlObjectPage, self).save() # control panels content_panels = Page.content_panels + [ # Default field PageChooserPanel is changed to third party AutocompletePanel # PageChooserPanel('disposer_id', 'mall.MlObjDisposer'), MultiFieldPanel([ AutocompletePanel('disposer_id', page_type='mall.MlObjDisposer'), FieldRowPanel([ FieldPanel('install_date'), FieldPanel('status_ok'), ], classname=None), ], heading="Кто расроряжается:", classname="collapsible collapsed"), MultiFieldPanel([ FieldRowPanel([ FieldPanel('is_enabled'), FieldPanel('is_critical'), ], classname=None), FieldRowPanel([ FieldPanel('diagnosed'), FieldPanel('have_maintenance'), ], classname=None), FieldRowPanel([ FieldPanel('call_down'), FieldPanel('have_to_be_repaired'), ], classname=None), FieldRowPanel([ FieldPanel('is_critically_broken', classname="col6"), ], classname=None), ], heading="Состояние объекта", classname="collapsible collapsed"), FieldPanel('tags'), FieldPanel('auto_tags'), FieldPanel('intro', classname="full"), MultiFieldPanel([ FieldPanel('description', classname=None), ], heading="Описание", classname="full collapsible collapsed"), MultiFieldPanel([ InlinePanel('ml_obj_docs', label="Приложение"), ], heading="Приложенные документы", classname="collapsible collapsed"), MultiFieldPanel([], heading='© Pavel Ushakov, BSD License'), ] promote_panels = Page.promote_panels + [ MultiFieldPanel([ InlinePanel('gallery_images', label="Фото объекта"), ], heading="Приложенные фото", classname="collapsible collapsed"), MultiFieldPanel([], heading='© Pavel Ushakov, BSD License'), ] settings_panels = Page.settings_panels + [ ] parent_page_types = ['mall.MlObjectIndexPage'] #subpage_types = [] class Meta: verbose_name = "объект" verbose_name_plural = "объекты"
class ExperiencePage(Page, CommentsMixIn): name = models.CharField(blank=True, default='', max_length=MAX_LENGTH) email = models.EmailField(blank=True, default='', max_length=MAX_LENGTH) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='experience_pages') posted = models.DateTimeField(default=timezone.now) experience = StreamField([ ('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('document', DocumentChooserBlock()), ]) importance = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA) data_reliability = models.CharField(max_length=6, choices=IMPORTANCE_CHOICES, default=NA) action = StreamField([ ('heading', blocks.CharBlock(classname="full title")), ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('document', DocumentChooserBlock()), ], blank=True) ranking_panels = [ FieldPanel('importance'), FieldPanel('data_reliability'), StreamFieldPanel('action'), ] content_panels = Page.content_panels + [ FieldPanel('name'), FieldPanel('email'), AutocompletePanel('user', target_model=settings.AUTH_USER_MODEL), FieldPanel('posted'), StreamFieldPanel('experience'), InlinePanel('comments', label="Comments"), ] search_fields = Page.search_fields + [ index.SearchField('experience'), ] subpage_types = [ ] # parent_page_types = [ # 'experiment.ExperimentPage', # 'homepage.ContentPage', # ] edit_handler = TabbedInterface( [ ObjectList(content_panels, heading='Content'), ObjectList(ranking_panels, heading="Ranking"), ObjectList(Page.promote_panels, heading='Promote'), ObjectList(Page.settings_panels, heading='Settings'), ] ) def __str__(self): return f'Experience Page {self.slug}' def serve(self, request): from .forms import CommentForm return self.add_comments_and_return(request, CommentForm)
class BlogPost(MetadataPageMixin, Page): author = models.ForeignKey('Person', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') date = models.DateField() blog_image = models.ForeignKey('wagtailimages.Image', null=True, blank=True, on_delete=models.PROTECT, related_name='+') promo_copy = RichTextField( blank=True, features=['bold', 'italic'], ) body = StreamField([ ('paragraph', blocks.RichTextBlock()), ('image', ImageChooserBlock()), ('youtube_embed', YouTubeVideoBlock()), ]) legacy_id = models.IntegerField(null=True, blank=True, unique=True) content_panels = Page.content_panels + [ AutocompletePanel('author', target_model='main.Person'), FieldPanel('date'), ImageChooserPanel('blog_image'), FieldPanel('promo_copy'), StreamFieldPanel('body'), ] promote_panels = [ MultiFieldPanel([ FieldPanel('slug'), FieldPanel('seo_title'), FieldPanel('show_in_menus'), FieldPanel( 'search_description', help_text=('Description to be used for social media sites. ' 'Otherwise, the promo text will be used.')), ImageChooserPanel( 'search_image', help_text=('Image to be used for social media sites. ' 'Otherwise, the blog image will be used.')), ], ugettext_lazy('Common page configuration')), ] def get_meta_description(self): return self.search_description or self.promo_copy def get_meta_image(self): return self.search_image or self.blog_image def get_context(self, request): context = super().get_context(request) context['recent_blog_posts'] = BlogPost.objects.live().public()\ .order_by('-date')[:5] return context parent_page_types = ['BlogIndex'] subpage_types = []
class Performer(Orderable): """ This is a performer in the sense of an instance of a performance. """ performance = ParentalKey('Performance', on_delete=models.CASCADE, related_name='performer') person = models.ForeignKey('Person', null=False, blank=False, on_delete=models.PROTECT, related_name='+') instrument = models.ForeignKey('InstrumentModel', null=False, blank=False, on_delete=models.PROTECT, related_name='+') def is_performer_live_public(self): return self.person.live and not self.person.get_view_restrictions() def get_performer(self): if (self.person.live and not self.person.get_view_restrictions()): return self.person return None def __str__(self): return "{} - {}".format( self.person.title, self.instrument, ) def save(self, *args, **kwargs): super().save(*args, **kwargs) # If not saved as a concert performer already, then # create a concert performer concert = self.performance.get_parent().specific if not ConcertPerformer.objects.filter(concert__pk=concert.id, person=self.person).exists(): ConcertPerformer.objects.create(concert=concert, person=self.person) def delete(self, *args, **kwargs): # Before deleting, check if other siblings list this person as a # performer, if so, than delete this particular performer, but leave # the concert performer alone. # If not, then delete the concert performer first, then delete concert = self.performance.get_parent().specific sibling_perfs = [ p.specific for p in self.performance.get_siblings(inclusive=False) ] found = False for perf in sibling_perfs: for person in perf.performer.all(): if person.id == self.person.id: found = True break if not found: try: ConcertPerformer.objects.get(concert__pk=concert.id, person=self.person).delete() except ConcertPerformer.DoesNotExist: pass super().delete(*args, **kwargs) panels = [ AutocompletePanel('person', target_model='main.Person'), AutocompletePanel('instrument', target_model='main.InstrumentModel'), ]
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_meta_description(self): """Determine what the page's meta and OpenGraph description should be Checks several different possible fields in order of preference. If none are found, returns an empty string, which is preferable to a generic description repeated on many pages. This method is overriding the standard one on CFGOVPage to factor in Ask CFPB AnswerPage-specific fields. """ preference_order = [ 'search_description', 'short_answer', 'first_text', ] candidates = {} if self.search_description: candidates['search_description'] = self.search_description if self.short_answer: candidates['short_answer'] = strip_tags(self.short_answer) if hasattr(self, 'answer_content'): for block in self.answer_content: if block.block_type == 'text': candidates['first_text'] = truncate_by_words_and_chars( strip_tags(block.value['content'].source), word_limit=35, char_limit=160) break for entry in preference_order: if candidates.get(entry): return candidates[entry] return '' def get_context(self, request, *args, **kwargs): # self.get_meta_description() is not called here because it is called # and added to the context by CFGOVPage's get_context() method. 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['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_by_words_and_chars(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
def test_target_model(self): autocomplete_panel = AutocompletePanel('owner').bind_to(model=House) self.assertEqual(autocomplete_panel.target_model, Person)
class DirectoryEntry(MetadataPageMixin, Page): objects = DirectoryEntryManager() landing_page_url = models.URLField( 'Landing page URL', max_length=255, unique=True ) onion_address = models.CharField( 'SecureDrop onion address', max_length=255, validators=[RegexValidator(regex=r'\.onion$', message="Enter a valid .onion address.")] ) added = models.DateTimeField(auto_now_add=True) organization_logo = models.ForeignKey( 'common.CustomImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', ) organization_logo_homepage = models.ForeignKey( 'common.CustomImage', null=True, blank=True, on_delete=models.SET_NULL, related_name='+', help_text='Optional second logo optimized to show up on dark backgrounds. For instances that are featured on the homepage.' ) organization_logo_is_title = models.BooleanField( default=False, help_text=( 'Logo will be displayed instead of the header on page. Recommended ' 'primarily for logos containing the full organization name on a ' 'white or transparent background' ) ) organization_description = models.CharField(max_length=95, blank=True, null=True, help_text="A micro description of your organization that will be displayed in the directory.") languages = ParentalManyToManyField( 'directory.Language', blank=True, verbose_name='Languages accepted', related_name='languages' ) countries = ParentalManyToManyField( 'directory.Country', blank=True, verbose_name='Countries', related_name='countries' ) topics = ParentalManyToManyField( 'directory.Topic', blank=True, verbose_name='Preferred topics', related_name='topics' ) DELISTED_REASONS = ( ('http', 'Mixed-content or no HTTPS'), ('no200', 'Non-200 status response'), ('down', 'Extended downtime (>1 week)'), ('other', 'Other'), ) delisted = models.CharField( max_length=10, blank=True, null=True, choices=DELISTED_REASONS, default=None, help_text=('If set, entry will not show up in the directory, but the ' 'page will still be live. Should be used for SecureDrop ' 'instances that are under review for detected issues.') ) WARNING_CHOICES = ( ('no_cdn', 'Use of CDN'), ('no_third_party_assets', 'Use of analytics or third party assets'), ('subdomain', 'Subdomain'), ('referrer_policy_set_to_no_referrer', 'Referer Policy'), ('safe_onion_address', 'Links to Onion Addresses'), ) permitted_domains_for_assets = ArrayField( models.TextField(), blank=True, default=list, help_text=('Comma-separated list of additional domains that will not trigger ' 'the cross domain asset warning for this landing page. ' 'Subdomains on domains in this list are ignored. For example, ' 'adding "news.bbc.co.uk" permits all assets from "bbc.co.uk".'), ) warnings_ignored = ChoiceArrayField( models.CharField( max_length=50, choices=WARNING_CHOICES, ), default=list, blank=True, help_text=('Landing page warnings that will not be shown to someone ' 'viewing this entry, even if they are in the scan results. ' 'Select multiples with shift or control click.'), ) content_panels = Page.content_panels + [ ReadOnlyPanel('added', heading='Date Added'), FieldPanel('landing_page_url'), FieldPanel('onion_address'), FieldPanel('organization_description'), MultiFieldPanel([ ImageChooserPanel('organization_logo'), ImageChooserPanel('organization_logo_homepage'), FieldPanel('organization_logo_is_title'), ], 'Logo'), AutocompletePanel('languages', 'directory.Language', is_single=False), AutocompletePanel('countries', 'directory.Country', is_single=False), AutocompletePanel('topics', 'directory.Topic', is_single=False), InlinePanel('owners', label='Owners'), InlinePanel('results', label='Results'), ] settings_panels = Page.settings_panels + [ FieldPanel('delisted'), FieldPanel('warnings_ignored'), FieldPanel('permitted_domains_for_assets'), ] search_fields_pgsql = ['title', 'landing_page_url', 'onion_address', 'organization_description'] def get_context(self, request): context = super(DirectoryEntry, self).get_context(request) try: result = self.get_live_result() except ScanResult.DoesNotExist: return context if not result: return context context['show_warnings'] = True messages = [] context['highest_warning_level'] = WarningLevel.NONE warnings = self.get_warnings(result) for warning in warnings: if warning.level.value > context['highest_warning_level'].value: context['highest_warning_level'] = warning.level messages.append( warning.message.format( 'This SecureDrop landing page', domain=url_to_domain(result.landing_page_url), ) ) context['warning_messages'] = messages return context def serve(self, request): owners = [sd_owner.owner for sd_owner in self.owners.all()] if request.user in owners: self.editable = True else: self.editable = False return super(DirectoryEntry, self).serve(request) def get_live_result(self): # Used in template to get the latest live result. return self.results.filter(live=True).order_by('-result_last_seen').first() def get_warnings(self, result): warnings = [] for warning in WARNINGS: if warning.name in self.warnings_ignored: continue if warning.test(result) == TestResult.FAIL: warnings.append(warning) return warnings def get_search_content(self): search_elements = get_search_content_by_fields(self, self.search_fields_pgsql) for field in ['languages', 'countries', 'topics']: for item in getattr(self, field).all(): search_elements.append(item.title) return search_elements def save(self, *args, **kwargs): from directory.models import ScanResult super(DirectoryEntry, self).save(*args, **kwargs) ScanResult.objects.filter(landing_page_url=self.landing_page_url).update(securedrop=self)
class Donation(ClusterableModel): PAYMENT_STATUS_CHOICES = [ (STATUS_COMPLETE, _(STATUS_COMPLETE.capitalize())), (STATUS_PROCESSING, _(STATUS_PROCESSING.capitalize())), (STATUS_REFUNDED, _(STATUS_REFUNDED.capitalize())), (STATUS_REVOKED, _(STATUS_REVOKED.capitalize())), (STATUS_FAILED, _(STATUS_FAILED.capitalize())), (STATUS_CANCELLED, _(STATUS_CANCELLED.capitalize())), ] user = models.ForeignKey('newstream_user.User', on_delete=models.SET_NULL, blank=True, null=True) form = models.ForeignKey('DonationForm', on_delete=models.SET_NULL, blank=True, null=True) gateway = models.ForeignKey('site_settings.PaymentGateway', on_delete=models.SET_NULL, null=True) subscription = models.ForeignKey('Subscription', on_delete=models.SET_NULL, blank=True, null=True) is_test = models.BooleanField(default=False) transaction_id = models.CharField(max_length=191, unique=True) donation_amount = models.DecimalField(max_digits=20, decimal_places=2) is_recurring = models.BooleanField(default=False) currency = models.CharField(max_length=20) payment_status = models.CharField(max_length=255, choices=PAYMENT_STATUS_CHOICES) donation_date = models.DateTimeField() guest_email = models.EmailField(blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey('newstream_user.User', related_name='donation_created_by', on_delete=models.SET_NULL, blank=True, null=True) linked_user_deleted = models.BooleanField(default=False) deleted = models.BooleanField(default=False) panels = [ AutocompletePanel('user', heading=_('User')), FieldPanel('form', heading=_('Donation Form')), FieldPanel('gateway', heading=_('Payment Gateway')), FieldPanel('subscription', heading=_('Subscription')), FieldPanel('is_test', heading=_('Is Test Donation?')), FieldPanel('transaction_id', heading=_('Transaction ID')), FieldPanel('donation_amount', heading=_('Donation amount')), FieldPanel('guest_email', heading=_('Guest Email - for non-registered donors')), FieldPanel('is_recurring', heading=_('Is Recurring Donation?')), FieldPanel('currency', heading=_('Currency')), FieldPanel('payment_status', heading=_('Payment status')), FieldPanel('donation_date', heading=_('Donation Date')), InlinePanel( 'metas', label=_('Donation Meta'), heading=_('Donation Meta Data'), help_text=_('Meta data about this donation is recorded here')), FieldPanel('linked_user_deleted', heading=_("Donor User Deleted?")), ] class Meta: ordering = ['-donation_date', '-created_at'] verbose_name = _('Donation') verbose_name_plural = _('Donations') def __str__(self): return '#' + str(self.id) + ' - ' + self.transaction_id def isRecurring(self): return 'Yes' if self.is_recurring else 'No' def donor_name(self): if self.user: return self.user.fullname else: return self.guest_email @property def is_user_first_donation(self): resultSet = DonationPaymentMeta.objects.filter( donation_id=self.id, field_key='is_user_first_donation') if len(resultSet) == 1: return resultSet[0].field_value return False @is_user_first_donation.setter def is_user_first_donation(self, val): pass @property def donation_frequency(self): return 'Monthly' if self.is_recurring else 'One-time' @donation_frequency.setter def donation_frequency(self, val): pass @property def donation_type_stripe(self): return 'recurring' if self.is_recurring else 'one_time' @donation_type_stripe.setter def donation_type_stripe(self, val): pass def isOnGoing(self): return 'Yes' if self.is_recurring and self.subscription.recurring_status == STATUS_ACTIVE else 'No'
class MlObjectIndexPage(Page): intro = models.TextField(verbose_name='краткое описание', blank=True) # ToDo: rewrite categories to "orderable" model category = models.ForeignKey('mall.MlCategory', on_delete=models.SET_NULL, null=True, blank=True, related_name='+') # logical block # is system normally operating # TODO rewrite it to integer and change bages to represent cached data # TODO write metod to populate cashed data on descendants update status_ok = models.IntegerField(default=0, verbose_name='статус OK') status_bad = models.IntegerField(default=0, verbose_name='статус Bad') is_enabled = models.IntegerField(default=0, verbose_name='включено') is_disabled = models.IntegerField(default=0, verbose_name='выключено') # is object periodically diagnosed diagnosed = models.IntegerField(default=0, verbose_name='произведен осмотр') have_to_be_diagnosed = models.IntegerField(default=0, verbose_name='требует осмотра') # have normal maintenance have_maintenance = models.IntegerField(default=0, verbose_name='пройдено ТО') need_service = models.IntegerField(default=0, verbose_name='требует ТО') # object have call-down to it's work call_down = models.IntegerField(default=0, verbose_name='есть замечания') # system working, but should be repaired have_to_be_repaired = models.IntegerField(default=0, verbose_name='требует ремонта') # is critical for parent system is_critical = models.IntegerField(default=0, verbose_name='критически важный элемент') # if critical and critically broken, then parent system broken too is_critically_broken = models.IntegerField(default=0, verbose_name='сломано') # settings fields # group is visible for tags. If False - group close local scope of tags is_visible_for_tags = models.BooleanField(default=True, verbose_name="группа видна для дочерних меток") partial_cache_reset = models.BooleanField(default=False, verbose_name="частичный сброс кэшированных данных") full_cache_reset = models.BooleanField(default=False, verbose_name="полный сброс кэшированных данных") # overriding default get_context to include only live objects, ordered by title def get_context(self, request, *args, **kwargs): context = super().get_context(request) # can be ordered by published date: order_by('-first_published_at') ml_objects = self.get_children().live().order_by('title') context['ml_objects'] = ml_objects return context # colored borders for objects def ml_list_alert_color(self): alert = "border-success" influence = 0 for key, value in obj_tag_dict.items(): if hasattr(self, key): if getattr(self, key): if influence < value[3]: influence = value[3] if key in ['status_ok', 'is_enabled', 'diagnosed', 'have_maintenance']: alert = "border-" + value[2] else: alert = "alert-" + value[2] return alert search_fields = Page.search_fields + [ index.SearchField('intro'), ] content_panels = Page.content_panels + [ FieldPanel('intro', classname="full"), # Default field SnippestChooserPanel is changed to third party AutocompletePanel # SnippetChooserPanel('category'), # PageChooserPanel('category'), AutocompletePanel('category', page_type='mall.MlCategory'), MultiFieldPanel([ FieldRowPanel([ FieldPanel('is_enabled'), FieldPanel('is_critical'), ], classname=None), FieldRowPanel([ FieldPanel('diagnosed'), FieldPanel('have_maintenance'), ], classname=None), FieldRowPanel([ FieldPanel('call_down'), FieldPanel('have_to_be_repaired'), ], classname=None), FieldRowPanel([ FieldPanel('is_critically_broken', classname="col6"), ], classname=None), ], heading="Состояние объекта", classname="collapsible collapsed"), MultiFieldPanel([], heading='© Pavel Ushakov, BSD License'), ] settings_panels = Page.settings_panels + [ FieldPanel('is_visible_for_tags'), FieldPanel('partial_cache_reset'), FieldPanel('full_cache_reset'), ] class Meta: verbose_name = "список объектов" verbose_name_plural = "списоки объектов" subpage_types = ['MlObjectPage', 'MlObjectIndexPage']