class CustomRichTextFieldPage(Page): body = RichTextField(editor='custom') content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('body'), ]
class AbstractCarouselItem(AbstractLinkFields): image = models.ForeignKey( 'tuiuiuimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) embed_url = models.URLField("Embed URL", blank=True) caption = models.CharField(max_length=255, blank=True) api_fields = ( 'image', 'embed_url', 'caption', ) + AbstractLinkFields.api_fields panels = [ ImageChooserPanel('image'), FieldPanel('embed_url'), FieldPanel('caption'), MultiFieldPanel(AbstractLinkFields.panels, "Link"), ] class Meta: abstract = True
class SimplePage(Page): content = models.TextField() content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('content'), ]
class DefaultRichTextFieldPage(Page): body = RichTextField() content_panels = [ FieldPanel('title', classname="full title"), FieldPanel('body'), ]
def setUp(self): self.EventPageForm = get_form_for_model(EventPage, form_class=TuiuiuAdminPageForm, formsets=[]) self.event = EventPage(title='Abergavenny sheepdog trials', date_from=date(2014, 7, 20), date_to=date(2014, 7, 21)) self.EndDatePanel = FieldPanel( 'date_to', classname='full-width').bind_to_model(EventPage)
def setUp(self): # a custom ObjectList for EventPage self.EventPageObjectList = ObjectList( [ FieldPanel('title', widget=forms.Textarea), FieldPanel('date_from'), FieldPanel('date_to'), InlinePanel('speakers', label="Speakers"), ], heading='Event details', classname="shiny").bind_to_model(EventPage)
def setUp(self): self.EventPageForm = get_form_for_model(EventPage, form_class=TuiuiuAdminPageForm, formsets=[]) self.event = EventPage(title='Abergavenny sheepdog trials', date_from=date(2014, 7, 20), date_to=date(2014, 7, 21)) self.DatesPanel = FieldRowPanel([ FieldPanel('date_from', classname='col4'), FieldPanel('date_to', classname='coltwo'), ]).bind_to_model(EventPage)
class Advert(ClusterableModel): url = models.URLField(null=True, blank=True) text = models.CharField(max_length=255) tags = TaggableManager(through=AdvertTag, blank=True) panels = [ FieldPanel('url'), FieldPanel('text'), FieldPanel('tags'), ] def __str__(self): return self.text
def setUp(self): # a custom tabbed interface for EventPage self.EventPageTabbedInterface = TabbedInterface([ ObjectList([ FieldPanel('title', widget=forms.Textarea), FieldPanel('date_from'), FieldPanel('date_to'), ], heading='Event details', classname="shiny"), ObjectList([ InlinePanel('speakers', label="Speakers"), ], heading='Speakers'), ]).bind_to_model(EventPage)
class LinkFields(models.Model): link_external = models.URLField("External link", blank=True) link_page = models.ForeignKey('tuiuiucore.Page', null=True, blank=True, related_name='+', on_delete=models.CASCADE) link_document = models.ForeignKey('tuiuiudocs.Document', null=True, blank=True, related_name='+', on_delete=models.CASCADE) @property def link(self): if self.link_page: return self.link_page.url elif self.link_document: return self.link_document.url else: return self.link_external panels = [ FieldPanel('link_external'), PageChooserPanel('link_page'), DocumentChooserPanel('link_document'), ] class Meta: abstract = True
class SectionedRichTextPageSection(Orderable): page = ParentalKey('tests.SectionedRichTextPage', related_name='sections', on_delete=models.CASCADE) body = RichTextField() panels = [FieldPanel('body')]
class ValidatedPage(Page): foo = models.CharField(max_length=255) base_form_class = ValidatedPageForm content_panels = Page.content_panels + [ FieldPanel('foo'), ]
class ContactFieldsMixin(models.Model): telephone = models.CharField(max_length=20, blank=True) email = models.EmailField(blank=True) address_1 = models.CharField(max_length=255, blank=True) address_2 = models.CharField(max_length=255, blank=True) city = models.CharField(max_length=255, blank=True) country = models.CharField(max_length=255, blank=True) post_code = models.CharField(max_length=10, blank=True) api_fields = ( 'telephone', 'email', 'address_1', 'address_2', 'city', 'country', 'post_code', ) panels = [ FieldPanel('telephone'), FieldPanel('email'), FieldPanel('address_1'), FieldPanel('address_2'), FieldPanel('city'), FieldPanel('country'), FieldPanel('post_code'), ] class Meta: abstract = True
class CustomRichBlockFieldPage(Page): body = StreamField([ ('rich_text', RichTextBlock(editor='custom')), ]) content_panels = [ FieldPanel('title', classname="full title"), StreamFieldPanel('body'), ]
class RichTextSection(models.Model): snippet = ParentalKey('MultiSectionRichTextSnippet', related_name='sections', on_delete=models.CASCADE) body = RichTextField() panels = [ FieldPanel('body'), ]
class RelatedLink(LinkFields): title = models.CharField(max_length=255, help_text="Link title") panels = [ FieldPanel('title'), MultiFieldPanel(LinkFields.panels, "Link"), ] class Meta: abstract = True
class BlogCategoryBlogPage(models.Model): category = models.ForeignKey(BlogCategory, related_name="+", on_delete=models.CASCADE) page = ParentalKey('ManyToManyBlogPage', related_name='categories', on_delete=models.CASCADE) panels = [ FieldPanel('category'), ]
class DefaultStreamPage(Page): body = StreamField([ ('text', CharBlock()), ('rich_text', RichTextBlock()), ('image', ImageChooserBlock()), ], default='') content_panels = [ FieldPanel('title'), StreamFieldPanel('body'), ]
class AbstractRelatedLink(AbstractLinkFields): title = models.CharField(max_length=255, help_text="Link title") api_fields = ('title', ) + AbstractLinkFields.api_fields panels = [ FieldPanel('title'), MultiFieldPanel(AbstractLinkFields.panels, "Link"), ] class Meta: abstract = True
class StreamPage(Page): body = StreamField([ ('text', CharBlock()), ('rich_text', RichTextBlock()), ('image', ExtendedImageChooserBlock()), ]) api_fields = ('body', ) content_panels = [ FieldPanel('title'), StreamFieldPanel('body'), ]
class EventPageSpeaker(Orderable, LinkFields): page = ParentalKey('tests.EventPage', related_name='speakers', on_delete=models.CASCADE) first_name = models.CharField("Name", max_length=255, blank=True) last_name = models.CharField("Surname", max_length=255, blank=True) image = models.ForeignKey('tuiuiuimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+') @property def name_display(self): return self.first_name + " " + self.last_name panels = [ FieldPanel('first_name'), FieldPanel('last_name'), ImageChooserPanel('image'), MultiFieldPanel(LinkFields.panels, "Link"), ]
class EventPageSpeaker(Orderable, AbstractLinkFields): page = ParentalKey('EventPage', related_name='speakers', on_delete=models.CASCADE) first_name = models.CharField("Name", max_length=255, blank=True) last_name = models.CharField("Surname", max_length=255, blank=True) image = models.ForeignKey( 'tuiuiuimages.Image', null=True, blank=True, on_delete=models.SET_NULL, related_name='+' ) api_fields = ( 'first_name', 'last_name', 'image', ) panels = [ FieldPanel('first_name'), FieldPanel('last_name'), ImageChooserPanel('image'), MultiFieldPanel(AbstractLinkFields.panels, "Link"), ]
class AdvertWithTabbedInterface(models.Model): url = models.URLField(null=True, blank=True) text = models.CharField(max_length=255) something_else = models.CharField(max_length=255) advert_panels = [ FieldPanel('url'), FieldPanel('text'), ] other_panels = [ FieldPanel('something_else'), ] edit_handler = TabbedInterface([ ObjectList(advert_panels, heading='Advert'), ObjectList(other_panels, heading='Other'), ]) def __str__(self): return self.text class Meta: ordering = ('text', )
class ManyToManyBlogPage(Page): """ A page type with two different kinds of M2M relation. We don't formally support these, but we don't want them to cause hard breakages either. """ body = RichTextField(blank=True) adverts = models.ManyToManyField(Advert, blank=True) blog_categories = models.ManyToManyField(BlogCategory, through=BlogCategoryBlogPage, blank=True) # make first_published_at editable on this page model settings_panels = Page.settings_panels + [ FieldPanel('first_published_at'), ]
class AbstractFormField(Orderable): """ Database Fields required for building a Django Form field. """ label = models.CharField(verbose_name=_('label'), max_length=255, help_text=_('The label of the form field')) field_type = models.CharField(verbose_name=_('field type'), max_length=16, choices=FORM_FIELD_CHOICES) required = models.BooleanField(verbose_name=_('required'), default=True) choices = models.TextField( verbose_name=_('choices'), blank=True, help_text= _('Comma separated list of choices. Only applicable in checkboxes, radio and dropdown.' )) default_value = models.CharField( verbose_name=_('default value'), max_length=255, blank=True, help_text=_( 'Default value. Comma separated values supported for checkboxes.')) help_text = models.CharField(verbose_name=_('help text'), max_length=255, blank=True) @property def clean_name(self): # unidecode will return an ascii string while slugify wants a # unicode string on the other hand, slugify returns a safe-string # which will be converted to a normal str return str(slugify(text_type(unidecode(self.label)))) panels = [ FieldPanel('label'), FieldPanel('help_text'), FieldPanel('required'), FieldPanel('field_type', classname="formbuilder-type"), FieldPanel('choices', classname="formbuilder-choices"), FieldPanel('default_value', classname="formbuilder-default"), ] class Meta: abstract = True ordering = ['sort_order']
def test_no_thousand_separators_in_js(self): """ Test that the USE_THOUSAND_SEPARATOR setting does not screw up the rendering of numbers (specifically maxForms=1000) in the JS initializer: https://github.com/tuiuiu/tuiuiu/pull/2699 https://github.com/tuiuiu/tuiuiu/issues/3227 """ SpeakerObjectList = ObjectList([ InlinePanel('speakers', label="Speakers", panels=[ FieldPanel('first_name', widget=forms.Textarea), ImageChooserPanel('image'), ]), ]).bind_to_model(EventPage) SpeakerInlinePanel = SpeakerObjectList.children[0] EventPageForm = SpeakerObjectList.get_form_class(EventPage) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) self.assertIn('maxForms: 1000', panel.render_js_init())
class Experiment(ClusterableModel): STATUS_CHOICES = [ ('draft', "Draft"), ('live', "Live"), ('completed', "Completed"), ] name = models.CharField(max_length=255) slug = models.SlugField(max_length=255) control_page = models.ForeignKey('tuiuiucore.Page', related_name='+', on_delete=models.CASCADE) goal = models.ForeignKey('tuiuiucore.Page', related_name='+', on_delete=models.SET_NULL, null=True, blank=True) status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft') winning_variation = models.ForeignKey('tuiuiucore.Page', related_name='+', on_delete=models.SET_NULL, null=True) panels = [ FieldPanel('name'), FieldPanel('slug'), PageChooserPanel('control_page'), InlinePanel('alternatives', label="Alternatives"), PageChooserPanel('goal'), FieldPanel('status'), ] def __init__(self, *args, **kwargs): super(Experiment, self).__init__(*args, **kwargs) self._initial_status = self.status def activate_alternative_draft_content(self): # For any alternative pages that are unpublished, copy the latest draft revision # to the main table (with is_live=False) so that the revision shown as an alternative # is not an out-of-date one for alternative in self.alternatives.select_related('page'): if not alternative.page.live: revision = alternative.page.get_latest_revision_as_page() revision.live = False revision.has_unpublished_changes = True revision.save() def get_variations(self): return [self.control_page] + [ alt.page for alt in self.alternatives.select_related('page') ] def get_variation_for_user(self, user_id): variations = self.get_variations() # choose uniformly from variations, based on a hash of user_id and experiment.slug hash_input = "{0}.{1}".format(self.slug, user_id) hash_str = sha1(hash_input.encode('utf-8')).hexdigest()[:7] variation_index = int(hash_str, 16) % len(variations) return variations[variation_index] def start_experiment_for_user(self, user_id, request): """ Record a new participant and return the variation for them to use """ variation = self.get_variation_for_user(user_id) get_backend().record_participant(self, user_id, variation, request) return variation def record_completion_for_user(self, user_id, request): backend = get_backend() variation = self.get_variation_for_user(user_id) backend.record_completion(self, user_id, variation, request) def select_winner(self, variation): self.winning_variation = variation self.status = 'completed' self.save() def __str__(self): return self.name
def test_render_with_panel_overrides(self): """ Check that inline panel renders the panels listed in the InlinePanel definition where one is specified """ SpeakerObjectList = ObjectList([ InlinePanel('speakers', label="Speakers", panels=[ FieldPanel('first_name', widget=forms.Textarea), ImageChooserPanel('image'), ]), ]).bind_to_model(EventPage) SpeakerInlinePanel = SpeakerObjectList.children[0] EventPageForm = SpeakerObjectList.get_form_class(EventPage) # SpeakerInlinePanel should instruct the form class to include a 'speakers' formset self.assertEqual(['speakers'], list(EventPageForm.formsets.keys())) event_page = EventPage.objects.get(slug='christmas') form = EventPageForm(instance=event_page) panel = SpeakerInlinePanel(instance=event_page, form=form) result = panel.render_as_field() # rendered panel should contain first_name rendered as a text area, but no last_name field self.assertIn('<label for="id_speakers-0-first_name">Name:</label>', result) self.assertIn('Father</textarea>', result) self.assertNotIn( '<label for="id_speakers-0-last_name">Surname:</label>', result) # test for #338: surname field should not be rendered as a 'stray' label-less field self.assertTagInHTML('<input id="id_speakers-0-last_name">', result, count=0, allow_extra_attrs=True) self.assertIn('<label for="id_speakers-0-image">Image:</label>', result) self.assertIn('Choose an image', result) # rendered panel must also contain hidden fields for id, DELETE and ORDER self.assertTagInHTML( '<input id="id_speakers-0-id" name="speakers-0-id" type="hidden">', result, allow_extra_attrs=True) self.assertTagInHTML( '<input id="id_speakers-0-DELETE" name="speakers-0-DELETE" type="hidden">', result, allow_extra_attrs=True) self.assertTagInHTML( '<input id="id_speakers-0-ORDER" name="speakers-0-ORDER" type="hidden">', result, allow_extra_attrs=True) # rendered panel must contain maintenance form for the formset self.assertTagInHTML( '<input id="id_speakers-TOTAL_FORMS" name="speakers-TOTAL_FORMS" type="hidden">', result, allow_extra_attrs=True) # render_js_init must provide the JS initializer self.assertIn('var panel = InlinePanel({', panel.render_js_init())
class TestFieldPanel(TestCase): def setUp(self): self.EventPageForm = get_form_for_model(EventPage, form_class=TuiuiuAdminPageForm, formsets=[]) self.event = EventPage(title='Abergavenny sheepdog trials', date_from=date(2014, 7, 20), date_to=date(2014, 7, 21)) self.EndDatePanel = FieldPanel( 'date_to', classname='full-width').bind_to_model(EventPage) def test_render_as_object(self): form = self.EventPageForm( { 'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22' }, instance=self.event) form.is_valid() field_panel = self.EndDatePanel(instance=self.event, form=form) result = field_panel.render_as_object() # check that label appears as a legend in the 'object' wrapper, # but not as a field label (that would be provided by ObjectList instead) self.assertIn('<legend>End date</legend>', result) self.assertNotIn('<label for="id_date_to">End date:</label>', result) # check that help text is not included (it's provided by ObjectList instead) self.assertNotIn('Not required if event is on a single day', result) # check that the populated form field is included self.assertIn('value="2014-07-22"', result) # there should be no errors on this field self.assertNotIn('<p class="error-message">', result) def test_render_as_field(self): form = self.EventPageForm( { 'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-22' }, instance=self.event) form.is_valid() field_panel = self.EndDatePanel(instance=self.event, form=form) result = field_panel.render_as_field() # check that label is output in the 'field' style self.assertIn('<label for="id_date_to">End date:</label>', result) self.assertNotIn('<legend>End date</legend>', result) # check that help text is included self.assertIn('Not required if event is on a single day', result) # check that the populated form field is included self.assertIn('value="2014-07-22"', result) # there should be no errors on this field self.assertNotIn('<p class="error-message">', result) def test_required_fields(self): result = self.EndDatePanel.required_fields() self.assertEqual(result, ['date_to']) def test_error_message_is_rendered(self): form = self.EventPageForm( { 'title': 'Pontypridd sheepdog trials', 'date_from': '2014-07-20', 'date_to': '2014-07-33' }, instance=self.event) form.is_valid() field_panel = self.EndDatePanel(instance=self.event, form=form) result = field_panel.render_as_field() self.assertIn('<p class="error-message">', result) self.assertIn('<span>Enter a valid date.</span>', result)
class PanelSettings(TestSetting): panels = [FieldPanel('title')]