예제 #1
0
class FilterableListForm(forms.Form):
    title_attrs = {'placeholder': 'Search for a specific word in item title'}
    topics_select_attrs = {
        'multiple': 'multiple',
        'data-placeholder': 'Search for topics',
    }
    authors_select_attrs = {
        'multiple': 'multiple',
        'data-placeholder': 'Search for authors'
    }

    title = forms.CharField(max_length=250,
                            required=False,
                            widget=widgets.TextInput(attrs=title_attrs))
    from_date = FilterableFromDateField()
    to_date = FilterableToDateField()
    categories = forms.MultipleChoiceField(
        required=False,
        choices=ref.page_type_choices,
        widget=widgets.CheckboxSelectMultiple())
    topics = MultipleChoiceFieldNoValidation(
        required=False,
        choices=[],
        widget=widgets.SelectMultiple(attrs=topics_select_attrs))
    authors = forms.MultipleChoiceField(
        required=False,
        choices=[],
        widget=widgets.SelectMultiple(attrs=authors_select_attrs))

    def __init__(self, *args, **kwargs):
        self.hostname = kwargs.pop('hostname')
        self.base_query = kwargs.pop('base_query')
        super(FilterableListForm, self).__init__(*args, **kwargs)

        pages = self.base_query.live_shared(self.hostname)
        page_ids = pages.values_list('id', flat=True)

        clean_categories(selected_categories=self.data.get('categories'))
        self.set_topics(page_ids)
        self.set_authors(page_ids)

    def get_page_set(self):
        query = self.generate_query()
        return self.base_query.filter(query).distinct().order_by(
            '-date_published')

    def prepare_options(self, arr):
        """
        Returns an ordered list of tuples of the format
        ('tag-slug-name', 'Tag Display Name')
        """
        arr = Counter(arr).most_common()  # Order by most to least common
        # Grab only the first tuple in the generated tuple,
        # which includes a count we do not need
        return [x[0] for x in arr]

    # Populate Topics' choices
    def set_topics(self, page_ids):
        tags = Tag.objects.filter(
            v1_cfgovtaggedpages_items__content_object__id__in=page_ids
        ).values_list('slug', 'name')

        options = self.prepare_options(arr=tags)
        most = options[:3]
        other = options[3:]

        self.fields['topics'].choices = \
            (('Most frequent', most),
             ('All other topics', other))

    # Populate Authors' choices
    def set_authors(self, page_ids):
        authors = Tag.objects.filter(
            v1_cfgovauthoredpages_items__content_object__id__in=page_ids
        ).values_list('slug', 'name')
        options = self.prepare_options(arr=authors)

        self.fields['authors'].choices = options

    def clean(self):
        cleaned_data = super(FilterableListForm, self).clean()
        from_date = cleaned_data.get('from_date')
        to_date = cleaned_data.get('to_date')
        # Check if both date_lte and date_gte are present
        # If the 'start' date is after the 'end' date, swap them
        if (from_date and to_date) and to_date < from_date:
            data = dict(self.data)
            data_to_date = data['to_date']
            self.cleaned_data['to_date'], data['to_date'] = \
                from_date, data['from_date']
            self.cleaned_data['from_date'], data['from_date'] = \
                to_date, data_to_date
            self.data = data
        return self.cleaned_data

    # Does the job of {{ field }}
    # In the template, you pass the field and the id and name you'd like to
    # render the field with.
    def render_with_id(self, field, attr_id):
        for f in self.fields:
            if field.html_name == f:
                self.fields[f].widget.attrs.update({'id': attr_id})
                self.set_field_html_name(self.fields[f], attr_id)
                return self[f]

    # Sets the html name by replacing the render method to use the given name.
    def set_field_html_name(self, field, new_name):
        """
        This creates wrapper around the normal widget rendering,
        allowing for a custom field name (new_name).
        """
        old_render = field.widget.render
        if isinstance(field.widget, widgets.SelectMultiple):
            field.widget.render = lambda name, value, attrs=None, choices=(): \
                old_render(new_name, value, attrs, choices)
        else:
            field.widget.render = lambda name, value, attrs=None: \
                old_render(new_name, value, attrs)

    # Generates a query by iterating over the zipped collection of
    # tuples.
    def generate_query(self):
        final_query = Q()
        if self.is_bound:
            for query, field_name in zip(self.get_query_strings(),
                                         self.declared_fields):
                if self.cleaned_data.get(field_name):
                    final_query &= \
                        Q((query, self.cleaned_data.get(field_name)))
        return final_query

    # Returns a list of query strings to associate for each field, ordered by
    # the field declaration for the form. Note: THEY MUST BE ORDERED IN THE
    # SAME WAY AS THEY ARE DECLARED IN THE FORM DEFINITION.
    def get_query_strings(self):
        return [
            'title__icontains',  # title
            'date_published__gte',  # from_date
            'date_published__lte',  # to_date
            'categories__name__in',  # categories
            'tags__slug__in',  # topics
            'authors__slug__in',  # authors
        ]
예제 #2
0
class BootstrapPicturePlugin(ImageAnnotationMixin, LinkPluginBase):
    name = _("Picture")
    model_mixins = (
        ImagePropertyMixin,
        LinkElementMixin,
    )
    module = 'Bootstrap'
    parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin']
    require_parent = True
    allow_children = False
    raw_id_fields = ('image_file', )
    admin_preview = False
    ring_plugin = 'PicturePlugin'
    render_template = 'cascade/bootstrap3/linked-picture.html'
    default_css_class = 'img-responsive'
    default_css_attributes = ('image_shapes', )
    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}
    fields = ('image_file', ) + LinkPluginBase.fields
    RESIZE_OPTIONS = (
        ('upscale', _("Upscale image")),
        ('crop', _("Crop image")),
        ('subject_location', _("With subject location")),
        ('high_resolution', _("Optimized for Retina")),
    )

    responsive_heights = GlossaryField(
        MultipleCascadingSizeWidget(BS3_BREAKPOINT_KEYS,
                                    allowed_units=['px', '%'],
                                    required=False),
        label=_("Adapt Picture Heights"),
        initial={
            'xs': '100%',
            'sm': '100%',
            'md': '100%',
            'lg': '100%'
        },
        help_text=
        _("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints."
          ),
    )

    responsive_zoom = GlossaryField(
        MultipleCascadingSizeWidget(BS3_BREAKPOINT_KEYS,
                                    allowed_units=['%'],
                                    required=False),
        label=_("Adapt Picture Zoom"),
        initial={
            'xs': '0%',
            'sm': '0%',
            'md': '0%',
            'lg': '0%'
        },
        help_text=
        _("Magnification of picture in percent for distinct Bootstrap's breakpoints."
          ),
    )

    resize_options = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
        label=_("Resize Options"),
        help_text=_("Options to use when resizing the image."),
        initial=['subject_location', 'high_resolution'])

    class Media:
        js = ['cascade/js/admin/pictureplugin.js']

    def get_form(self, request, obj=None, **kwargs):
        reduce_breakpoints(self, 'responsive_heights')
        image_file = ModelChoiceField(queryset=Image.objects.all(),
                                      required=False,
                                      label=_("Image"))
        Form = type(
            str('ImageForm'), (
                ImageFormMixin,
                getattr(LinkForm, 'get_form_class')(),
            ), {
                'LINK_TYPE_CHOICES': ImageFormMixin.LINK_TYPE_CHOICES,
                'image_file': image_file
            })
        kwargs.update(form=Form)
        return super(BootstrapPicturePlugin,
                     self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        # image shall be rendered in a responsive context using the picture element
        elements = get_picture_elements(context, instance)
        fluid = instance.get_complete_glossary().get('fluid') == 'on'
        context.update({
            'is_responsive': True,
            'instance': instance,
            'is_fluid': fluid,
            'placeholder': placeholder,
            'elements': elements,
        })
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(BootstrapPicturePlugin, cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapPicturePlugin, cls).get_identifier(obj)
        try:
            content = force_text(obj.image)
        except AttributeError:
            content = _("No Picture")
        return format_html('{0}{1}', identifier, content)
예제 #3
0
class BaseForm(forms.Form):
    """
    required:是否必填
    error_messages:錯誤提示(要透過{{ obj.errors.user.0 }}調用)
    widget:訂製html插件(重要)
    label:訂製標籤名(要透過{{ obj.user.label }}調用)
    initial:默認值
    validators:自訂製驗證規則
    disabled:是否禁止編輯
    """
    name = forms.CharField(
        initial="jamie",
        max_length=32,
        min_length=3,
        required=True,
        error_messages={
            "invalid": "請輸入正確格式",  # 所有格式錯誤都用invalid
            "min_length": "請輸入最少3個字元",
        })
    age = forms.IntegerField(initial=18,
                             max_value=100,
                             min_value=0,
                             error_messages={
                                 "invalid": "請輸入數字格式",
                                 "min_value": "最好會有負的年齡",
                             })
    email = forms.EmailField(initial="*****@*****.**", )
    tall = forms.DecimalField(initial=178.87, )
    file = forms.FileField(required=False, )
    # 單選
    city = fields.ChoiceField(
        choices=[(1, "台北"), (2, "台中"), (3, "高雄")],  # (value, 顯示文字)
        initial=3,  # 默認value=3
    )
    # 多選
    hobby = fields.MultipleChoiceField(
        choices=[(1, "籃球"), (2, "電動"), (3, "小說")],  # (value, 顯示文字)
        initial=[1, 2],
    )
    birth = fields.DateField(initial="2000-01-01")  # 格式:2015-09-01
    # datetime = fields.DateTimeField()  # 格式:2015-09-01 11:12
    radio = fields.ChoiceField(
        choices=[(1, 'man'), (2, 'woman')],
        widget=widgets.RadioSelect(),
        initial=1,
    )
    checkbox = fields.MultipleChoiceField(
        choices=[(1, 'man'), (2, 'woman')],
        widget=widgets.CheckboxSelectMultiple(),
        initial=[1, 2],
    )
    myField = fields.CharField(
        # 自定義正則(一)
        # validators可設置多個RegexValidator
        # RegexValidator(正則, 錯誤信息)
        validators=[
            RegexValidator(r"^[0-9]+$", "請輸入數字"),
            RegexValidator(r"^87[0-9]+$", "請以87開頭")
        ],
        initial="87123",
    )
    # 自定義正則(二)
    # fields.RegexField(正則),只可設置單個正則
    myField2 = fields.RegexField(
        r"^[0-9]+$",
        error_messages={
            "invalid": "請輸入數字",
        },
        initial="123456",
    )
예제 #4
0
    def get_form(self, request, obj=None, **kwargs):
        from cmsplugin_cascade.models import PluginExtraFields
        from .config import PluginExtraFieldsConfig

        glossary_fields = list(
            kwargs.pop('glossary_fields', self.glossary_fields))
        clsname = self.__class__.__name__
        try:
            site = get_current_site(request)
            extra_fields = PluginExtraFields.objects.get(plugin_type=clsname,
                                                         site=site)
        except ObjectDoesNotExist:
            extra_fields = app_settings.CMSPLUGIN_CASCADE[
                'plugins_with_extra_fields'].get(clsname)

        if isinstance(extra_fields,
                      (PluginExtraFields, PluginExtraFieldsConfig)):
            # add a text input field to let the user name an ID tag for this HTML element
            if extra_fields.allow_id_tag:
                glossary_fields.append(
                    GlossaryField(widgets.TextInput(),
                                  label=_("Named Element ID"),
                                  name='extra_element_id'))

            # add a select box to let the user choose one or more CSS classes
            class_names = extra_fields.css_classes.get('class_names',
                                                       '').replace(' ', '')
            if class_names:
                choices = [(clsname, clsname)
                           for clsname in class_names.split(',')]
                if extra_fields.css_classes.get('multiple'):
                    widget = widgets.CheckboxSelectMultiple(choices=choices)
                else:
                    widget = widgets.Select(
                        choices=((None, _("Select CSS")), ) + tuple(choices))
                glossary_fields.append(
                    GlossaryField(
                        widget,
                        label=_("Customized CSS Classes"),
                        name='extra_css_classes',
                        help_text=
                        _("Customized CSS classes to be added to this element."
                          )))

            # add input fields to let the user enter styling information
            for style, choices_tuples in app_settings.CMSPLUGIN_CASCADE[
                    'extra_inline_styles'].items():
                inline_styles = extra_fields.inline_styles.get(
                    'extra_fields:{0}'.format(style))
                if not inline_styles:
                    continue
                Widget = choices_tuples[1]
                if issubclass(Widget, MultipleCascadingSizeWidget):
                    key = 'extra_inline_styles:{0}'.format(style)
                    allowed_units = extra_fields.inline_styles.get(
                        'extra_units:{0}'.format(style)).split(',')
                    widget = Widget(inline_styles,
                                    allowed_units=allowed_units,
                                    required=False)
                    glossary_fields.append(
                        GlossaryField(widget, label=style, name=key))
                else:
                    for inline_style in inline_styles:
                        key = 'extra_inline_styles:{0}'.format(inline_style)
                        label = '{0}: {1}'.format(style, inline_style)
                        glossary_fields.append(
                            GlossaryField(Widget(), label=label, name=key))

        kwargs.update(glossary_fields=glossary_fields)
        return super(ExtraFieldsMixin, self).get_form(request, obj, **kwargs)
예제 #5
0
class BootstrapJumbotronPlugin(BootstrapPluginBase):
    name = _("Jumbotron")
    model_mixins = (ContainerGridMixin, ImagePropertyMixin, ImageBackgroundMixin)
    form = JumbotronPluginForm
    require_parent = False
    parent_classes = ('BootstrapColumnPlugin',)
    allow_children = True
    alien_child_classes = True
    raw_id_fields = ['image_file']
    fields = ['glossary', 'image_file']
    render_template = 'cascade/bootstrap4/jumbotron.html'
    ring_plugin = 'JumbotronPlugin'
    ATTACHMENT_CHOICES = ['scroll', 'fixed', 'local']
    VERTICAL_POSITION_CHOICES = ['top', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'bottom']
    HORIZONTAL_POSITION_CHOICES = ['left', '10%', '20%', '30%', '40%', 'center', '60%', '70%', '80%', '90%', 'right']
    REPEAT_CHOICES = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat']
    SIZE_CHOICES = ['auto', 'width/height', 'cover', 'contain']
    container_glossary_fields = (
        GlossaryField(
            ContainerBreakpointsWidget(choices=get_widget_choices()),
            label=_("Available Breakpoints"),
            name='breakpoints',
            initial=app_settings.CMSPLUGIN_CASCADE['bootstrap4']['default_bounds'].keys(),
            help_text=_("Supported display widths for Bootstrap's grid system.")
        ),
        GlossaryField(
            MultipleCascadingSizeWidget([bp.name for bp in Breakpoint], allowed_units=['px', '%'], required=False),
            label=_("Adapt Picture Heights"),
            name='container_max_heights',
            initial={'xs': '100%', 'sm': '100%', 'md': '100%', 'lg': '100%', 'xl': '100%'},
            help_text=_("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints.")
        ),
        GlossaryField(
            widgets.CheckboxSelectMultiple(choices=BootstrapPicturePlugin.RESIZE_OPTIONS),
            label=_("Resize Options"),
            name='resize_options',
            initial=['crop', 'subject_location', 'high_resolution'],
            help_text=_("Options to use when resizing the image.")
        ),
    )

    background_color = GlossaryField(
        ColorPickerWidget(),
        label=_("Background color"),
    )

    background_repeat = GlossaryField(
        widgets.RadioSelect(choices=[(c, c) for c in REPEAT_CHOICES]),
        initial='no-repeat',
        label=_("This property specifies how an image repeates."),
    )

    background_attachment = GlossaryField(
        widgets.RadioSelect(choices=[(c, c) for c in ATTACHMENT_CHOICES]),
        initial='local',
        label=_("This property specifies how to move the background relative to the viewport."),
    )

    background_vertical_position = GlossaryField(
        widgets.Select(choices=[(c, c) for c in VERTICAL_POSITION_CHOICES]),
        initial='center',
        label=_("This property moves a background image vertically within its container."),
    )

    background_horizontal_position = GlossaryField(
        widgets.Select(choices=[(c, c) for c in HORIZONTAL_POSITION_CHOICES]),
        initial='center',
        label=_("This property moves a background image horizontally within its container."),
    )

    background_size = GlossaryField(
        widgets.RadioSelect(choices=[(c, c) for c in SIZE_CHOICES]),
        initial='auto',
        label=_("Background size"),
        help_text=_("This property specifies how an image is sized."),
    )

    background_width_height = GlossaryField(
        MultipleCascadingSizeWidget(['width', 'height'], allowed_units=['px', '%'],
                                    required=False),
        label=_("Background width and height"),
        help_text=_("This property specifies the width and height of a background image."),
    )
    footnote_html = """
<p>For more information about the Jumbotron please read </p>
    """

    class Media:
        js = ['cascade/js/admin/jumbotronplugin.js']

    def get_form(self, request, obj=None, **kwargs):
        if self.get_parent_instance(request, obj) is None:
            # we only ask for breakpoints, if the jumbotron is the root of the placeholder
            kwargs.update(glossary_fields=list(self.container_glossary_fields))
            kwargs['glossary_fields'].extend(self.glossary_fields)
        form = super(BootstrapJumbotronPlugin, self).get_form(request, obj, **kwargs)
        return form

    def render(self, context, instance, placeholder):
        # image shall be rendered in a responsive context using the ``<picture>`` element
        try:
            elements = get_picture_elements(instance)
        except Exception as exc:
            logger.warning("Unable generate picture elements. Reason: {}".format(exc))
        else:
            if instance.child_plugin_instances and instance.child_plugin_instances[0].plugin_type == 'BootstrapRowPlugin':
                padding='padding: {0}px {0}px;'.format(int( app_settings.CMSPLUGIN_CASCADE['bootstrap4']['gutter']/2))
                context.update({'add_gutter_if_child_is_BootstrapRowPlugin': padding,})
            context.update({
                'elements': [e for e in elements if 'media' in e] if elements else [],
                'CSS_PREFIXES': app_settings.CSS_PREFIXES,
            })
        return self.super(BootstrapJumbotronPlugin, self).render(context, instance, placeholder)

    @classmethod
    def sanitize_model(cls, obj):
        sanitized = False
        # if the jumbotron is the root of the placeholder, we consider it as "fluid"
        obj.glossary['fluid'] = obj.parent is None
        super(BootstrapJumbotronPlugin, cls).sanitize_model(obj)
        grid_container = obj.get_bound_plugin().get_grid_instance()
        obj.glossary.setdefault('media_queries', {})
        for bp, bound in grid_container.bounds.items():
            obj.glossary['media_queries'].setdefault(bp.name, {})
            width = round(bound.max)
            if obj.glossary['media_queries'][bp.name].get('width') != width:
                obj.glossary['media_queries'][bp.name]['width'] = width
                sanitized = True
            if obj.glossary['media_queries'][bp.name].get('media') != bp.media_query:
                obj.glossary['media_queries'][bp.name]['media'] = bp.media_query
                sanitized = True
        return sanitized

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = cls.super(BootstrapJumbotronPlugin, cls).get_css_classes(obj)
        if obj.glossary.get('fluid'):
            css_classes.append('jumbotron-fluid')
        else:
            css_classes.append('jumbotron')
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapJumbotronPlugin, cls).get_identifier(obj)
        try:
            content = obj.image.name or obj.image.original_filename
        except AttributeError:
            content = _("Without background image")
        return format_html('{0}{1}', identifier, content)
예제 #6
0
class BootstrapGalleryPlugin(CascadePluginBase):
    name = _("Gallery")
    module = 'Bootstrap'
    intro_html = _(
        "<p>Add images, which make up a set to be used as gallery.</p>"
        "<p>All thumbnails for these images are resized to the same widths and heights.</p>"
    )
    parent_classes = ['BootstrapColumnPlugin']
    require_parent = True
    allow_children = False
    raw_id_fields = ('image_file', )
    text_enabled = True
    admin_preview = False
    render_template = 'cascade/bootstrap3/gallery.html'
    default_css_attributes = ('image_shapes', )
    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}
    inlines = (GalleryPluginInline, )
    SHAPE_CHOICES = (('img-responsive', _("Responsive")), )
    RESIZE_OPTIONS = (
        ('upscale', _("Upscale image")),
        ('crop', _("Crop image")),
        ('subject_location', _("With subject location")),
        ('high_resolution', _("Optimized for Retina")),
    )

    image_shapes = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES),
        label=_("Image Responsiveness"),
        initial=['img-responsive'],
    )

    image_width_responsive = GlossaryField(
        CascadingSizeWidget(allowed_units=['%'], required=False),
        label=_("Responsive Image Width"),
        initial='100%',
        help_text=_(
            "Set the image width in percent relative to containing element."),
    )

    image_width_fixed = GlossaryField(
        CascadingSizeWidget(allowed_units=['px'], required=False),
        label=_("Fixed Image Width"),
        help_text=_("Set a fixed image width in pixels."),
    )

    image_height = GlossaryField(
        CascadingSizeWidget(allowed_units=['px', '%'], required=False),
        label=_("Adapt Image Height"),
        help_text=
        _("Set a fixed height in pixels, or percent relative to the image width."
          ),
    )

    thumbnail_width = GlossaryField(
        CascadingSizeWidget(allowed_units=['px']),
        label=_("Thumbnail Width"),
        help_text=_("Set a fixed thumbnail width in pixels."),
    )

    thumbnail_height = GlossaryField(
        CascadingSizeWidget(allowed_units=['px', '%']),
        label=_("Thumbnail Height"),
        help_text=
        _("Set a fixed height in pixels, or percent relative to the thumbnail width."
          ),
    )

    resize_options = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
        label=_("Resize Options"),
        help_text=_("Options to use when resizing the image."),
        initial=['crop', 'subject_location', 'high_resolution'],
    )

    class Media:
        js = resolve_dependencies('cascade/js/admin/imageplugin.js')

    def get_form(self, request, obj=None, **kwargs):
        utils.reduce_breakpoints(self, 'responsive_heights')
        form = super(BootstrapGalleryPlugin,
                     self).get_form(request, obj, **kwargs)
        return form

    def render(self, context, instance, placeholder):
        gallery_instances = []
        options = dict(instance.get_complete_glossary())
        for inline_element in instance.sortinline_elements.all():
            # since inline_element requires the property `image`, add ImagePropertyMixin
            # to its class during runtime
            try:
                ProxyModel = create_proxy_model('GalleryImage',
                                                (ImagePropertyMixin, ),
                                                SortableInlineCascadeElement,
                                                module=__name__)
                inline_element.__class__ = ProxyModel
                options.update(
                    inline_element.glossary, **{
                        'image_width_fixed': options['thumbnail_width'],
                        'image_height': options['thumbnail_height'],
                        'is_responsive': False,
                    })
                thumbnail_tags = utils.get_image_tags(context, inline_element,
                                                      options)
                for key, val in thumbnail_tags.items():
                    setattr(inline_element, key, val)
                gallery_instances.append(inline_element)
            except (KeyError, AttributeError):
                pass
        inline_styles = instance.glossary.get('inline_styles', {})
        inline_styles.update(width=options['thumbnail_width'])
        instance.glossary['inline_styles'] = inline_styles
        context.update(
            dict(instance=instance,
                 placeholder=placeholder,
                 gallery_instances=gallery_instances))
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(BootstrapGalleryPlugin, cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapGalleryPlugin, cls).get_identifier(obj)
        num_elems = obj.sortinline_elements.count()
        content = ungettext_lazy("with {0} image", "with {0} images",
                                 num_elems).format(num_elems)
        return format_html('{0}{1}', identifier, content)
예제 #7
0
파일: company.py 프로젝트: tainaml/p_gs
class CompanyForm(MaterialModelForm):

    use_required_attribute = False

    categories = TaxonomyModelChoiceField(
        queryset=CompanyProxy.list_categories(),
        widget=widgets.CheckboxSelectMultiple(attrs={'class': 'hidden'}))

    communities = TaxonomyModelChoiceField(
        queryset=CompanyProxy.list_communities(),
        widget=widgets.SelectMultiple(attrs={'class': 'shows'}))

    email = forms.EmailField(required=True, label=_("Email"))

    request_user = None

    def is_valid(self):

        valid = super(CompanyForm, self).is_valid()

        if 'email' in self.cleaned_data:
            email = self.cleaned_data['email']
            User = get_user_model()
            emails_exists = User.objects.filter(email=email).exists()
            if emails_exists and (
                (self.instance.user and self.instance.user.email != email)
                    or not self.instance.id):
                self.add_error(
                    'email',
                    ValidationError(('Email already exists.'),
                                    code='email_exists'))
                valid = False

        return valid

    def __init__(self, *args, **kwargs):

        instance = kwargs.get('instance')

        if instance:
            initial = kwargs.get('initial', {})
            initial.update({
                'categories':
                instance.taxonomies.filter(term__slug='categoria'),
                'communities':
                instance.taxonomies.filter(term__slug='comunidade'),
                'email':
                instance.user.email
            })

            kwargs.update({'initial': initial})

        super(CompanyForm, self).__init__(*args, **kwargs)
        self.fields['communities'].widget.label = _("Comunidades")

    class Meta:

        model = CompanyProxy
        exclude = ('user', 'taxonomies', 'members')
        labels = {
            'name': u'Nome da Organização',
            'website': 'URL',
            'city': 'Cidade'
        }
        widgets = {
            "logo": forms.FileInput
            # "name": InputTextMaterial
        }

    def set_request_user(self, user):

        self.request_user = user

    def save(self, commit=True):
        self.instance.email = self.cleaned_data['email']
        instance = super(CompanyForm, self).save(commit=commit)

        return instance
예제 #8
0
class BootstrapImagePlugin(LinkPluginBase):
    name = _("Image")
    model_mixins = (
        ImagePropertyMixin,
        LinkElementMixin,
    )
    form = LinkedImageForm
    module = 'Bootstrap'
    parent_classes = ['BootstrapColumnPlugin']
    require_parent = True
    allow_children = False
    raw_id_fields = ('image_file', )
    text_enabled = True
    admin_preview = False
    render_template = 'cascade/bootstrap3/linked-image.html'
    default_css_attributes = ('image-shapes', )
    html_tag_attributes = {'image-title': 'title', 'alt-tag': 'tag'}
    fields = (
        'image_file',
        'glossary',
        (
            'link_type',
            'cms_page',
            'ext_url',
        ),
    )
    SHAPE_CHOICES = (
        ('img-responsive', _("Responsive")),
        ('img-rounded', _('Rounded')),
        ('img-circle', _('Circle')),
        ('img-thumbnail', _('Thumbnail')),
    )
    RESIZE_OPTIONS = (
        ('upscale', _("Upscale image")),
        ('crop', _("Crop image")),
        ('subject_location', _("With subject location")),
        ('high_resolution', _("Optimized for Retina")),
    )
    glossary_fields = (
        PartialFormField(
            'image-title',
            widgets.TextInput(),
            label=_('Image Title'),
            help_text=
            _("Caption text added to the 'title' attribute of the <img> element."
              ),
        ),
        PartialFormField(
            'alt-tag',
            widgets.TextInput(),
            label=_('Alternative Description'),
            help_text=
            _("Textual description of the image added to the 'alt' tag of the <img> element."
              ),
        ),
    ) + LinkPluginBase.glossary_fields + (
        PartialFormField('image-shapes',
                         widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES),
                         label=_("Image Shapes"),
                         initial=['img-responsive']),
        PartialFormField(
            'image-width-responsive',
            CascadingSizeWidget(allowed_units=['%'], required=False),
            label=_("Responsive Image Width"),
            initial='100%',
            help_text=_(
                "Set the image width in percent relative to containing element."
            ),
        ),
        PartialFormField(
            'image-width-fixed',
            CascadingSizeWidget(allowed_units=['px'], required=False),
            label=_("Fixed Image Width"),
            help_text=_("Set a fixed image width in pixels."),
        ),
        PartialFormField(
            'image-height',
            CascadingSizeWidget(allowed_units=['px', '%'], required=False),
            label=_("Adapt Image Height"),
            help_text=
            _("Set a fixed height in pixels, or percent relative to the image width."
              ),
        ),
        PartialFormField(
            'resize-options',
            widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
            label=_("Resize Options"),
            help_text=_("Options to use when resizing the image."),
            initial=['subject_location', 'high_resolution']),
    )

    class Media:
        js = resolve_dependencies('cascade/js/admin/imageplugin.js')

    def get_form(self, request, obj=None, **kwargs):
        utils.reduce_breakpoints(self, 'responsive-heights')
        return super(BootstrapImagePlugin,
                     self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        is_responsive = 'img-responsive' in instance.glossary.get(
            'image-shapes', [])
        tags = utils.get_image_tags(context, instance, is_responsive)
        if tags:
            extra_styles = tags.pop('extra_styles')
            inline_styles = instance.glossary.get('inline_styles', {})
            inline_styles.update(extra_styles)
            instance.glossary['inline_styles'] = inline_styles
            context.update(
                dict(instance=instance, placeholder=placeholder, **tags))
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(BootstrapImagePlugin, cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapImagePlugin, cls).get_identifier(obj)
        try:
            content = force_text(obj.image)
        except AttributeError:
            content = _("No Image")
        return format_html('{0}{1}', identifier, content)
예제 #9
0
class BootstrapContainerPlugin(BootstrapPluginBase):
    name = _("Container")
    require_parent = False
    parent_classes = []
    form = BootstrapContainerForm
    breakpoints = list(BS3_BREAKPOINTS)
    i = 0
    widget_choices = []
    for br, br_options in BS3_BREAKPOINTS.items():
        if i == 0:
            widget_choices.append(
                (br, '{} (<{}px)'.format(br_options[2], br_options[0])))
        elif i == len(breakpoints[:-1]):
            widget_choices.append(
                (br, '{} (≥{}px)'.format(br_options[2], br_options[0])))
        else:
            widget_choices.append((br, '{} (≥{}px and <{}px)'.format(
                br_options[2], br_options[0],
                BS3_BREAKPOINTS[breakpoints[(i + 1)]][0])))
        i += 1

    WIDGET_CHOICES = tuple(widget_choices)

    glossary_fields = (
        PartialFormField(
            'breakpoints',
            widgets.CheckboxSelectMultiple(
                choices=WIDGET_CHOICES, renderer=ContainerBreakpointsRenderer),
            label=_('Available Breakpoints'),
            initial=breakpoints[::-1],
            help_text=_(
                "Supported display widths for Bootstrap's grid system.")),
        PartialFormField(
            'fluid',
            widgets.CheckboxInput(),
            label=_('Fluid Container'),
            initial=False,
            help_text=_(
                "Changing your outermost '.container' to '.container-fluid'.")
        ),
    )
    glossary_variables = ['container_max_widths', 'media_queries']

    class Media:
        css = {
            'all':
            ('//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css',
             )
        }
        js = ['cascade/js/admin/containerplugin.js']

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapContainerPlugin, cls).get_identifier(obj)
        breakpoints = obj.glossary.get('breakpoints')
        content = obj.glossary.get('fluid') and '(fluid) ' or ''
        if breakpoints:
            devices = ', '.join(
                [force_text(BS3_BREAKPOINTS[bp][2]) for bp in breakpoints])
            content = _("{0}for {1}").format(content, devices)
        return format_html('{0}{1}', identifier, content)

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(BootstrapContainerPlugin, cls).get_css_classes(obj)
        if obj.glossary.get('fluid'):
            css_classes.append('container-fluid')
        else:
            css_classes.append('container')
        return css_classes

    def save_model(self, request, obj, form, change):
        super(BootstrapContainerPlugin,
              self).save_model(request, obj, form, change)
        obj.sanitize_children()

    @classmethod
    def sanitize_model(cls, obj):
        sanitized = super(BootstrapContainerPlugin, cls).sanitize_model(obj)
        parent_glossary = obj.get_parent_glossary()
        # compute the max width and the required media queries for each chosen breakpoint
        obj.glossary['container_max_widths'] = max_widths = {}
        obj.glossary['media_queries'] = media_queries = {}
        breakpoints = obj.glossary.get('breakpoints', [])
        last_index = len(breakpoints) - 1
        for index, bp in enumerate(breakpoints):
            try:
                max_widths[bp] = parent_glossary['container_max_widths'][bp]
            except KeyError:
                if obj.glossary.get('fluid'):
                    if bp == 'lg':
                        max_widths[bp] = settings.CMSPLUGIN_CASCADE[
                            'bootstrap3']['fluid-lg-width']
                    else:
                        max_widths[bp] = BS3_BREAKPOINTS[bp][0]
                else:
                    max_widths[bp] = BS3_BREAKPOINTS[bp][3]
            if last_index > 0:
                if index == 0:
                    next_bp = breakpoints[1]
                    media_queries[bp] = [
                        '(max-width: {0}px)'.format(
                            BS3_BREAKPOINTS[next_bp][0])
                    ]
                elif index == last_index:
                    media_queries[bp] = [
                        '(min-width: {0}px)'.format(BS3_BREAKPOINTS[bp][0])
                    ]
                else:
                    next_bp = breakpoints[index + 1]
                    media_queries[bp] = [
                        '(min-width: {0}px)'.format(BS3_BREAKPOINTS[bp][0]),
                        '(max-width: {0}px)'.format(
                            BS3_BREAKPOINTS[next_bp][0])
                    ]
        return sanitized

    def get_parent_classes(self, slot, page):
        if self.cms_plugin_instance and self.cms_plugin_instance.parent:
            # enforce that a ContainerPlugin can't have a parent
            return []
        return super(BootstrapContainerPlugin,
                     self).get_parent_classes(slot, page)
예제 #10
0
class CarouselPlugin(BootstrapPluginBase):
    name = _("Carousel")
    form = CarouselSlidesForm
    default_css_class = 'carousel'
    default_css_attributes = ('options', )
    parent_classes = ['BootstrapColumnPlugin']
    render_template = 'cascade/bootstrap3/carousel.html'
    default_inline_styles = {'overflow': 'hidden'}
    fields = (
        'num_children',
        'glossary',
    )
    DEFAULT_CAROUSEL_ATTRIBUTES = {'data-ride': 'carousel'}
    OPTION_CHOICES = (
        ('slide', _("Animate")),
        ('pause', _("Pause")),
        ('wrap', _("Wrap")),
    )
    glossary_fields = (
        PartialFormField(
            'interval',
            NumberInputWidget(attrs={
                'size': '2',
                'style': 'width: 4em;',
                'min': '1'
            }),
            label=_("Interval"),
            initial=5,
            help_text=_("Change slide after this number of seconds."),
        ),
        PartialFormField(
            'options',
            widgets.CheckboxSelectMultiple(choices=OPTION_CHOICES),
            label=_('Options'),
            initial=['slide', 'wrap', 'pause'],
            help_text=_("Adjust interval for the carousel."),
        ),
        PartialFormField(
            'container_max_heights',
            MultipleCascadingSizeWidget(CASCADE_BREAKPOINTS_LIST,
                                        allowed_units=['px']),
            label=_("Carousel heights"),
            initial={
                'xs': '100px',
                'sm': '150px',
                'md': '200px',
                'lg': '300px'
            },
            help_text=
            _("Heights of Carousel in pixels for distinct Bootstrap's breakpoints."
              ),
        ),
        PartialFormField(
            'resize-options',
            widgets.CheckboxSelectMultiple(
                choices=BootstrapPicturePlugin.RESIZE_OPTIONS),
            label=_("Resize Options"),
            help_text=_("Options to use when resizing the image."),
            initial=['upscale', 'crop', 'subject_location',
                     'high_resolution']),
    )

    def get_form(self, request, obj=None, **kwargs):
        utils.reduce_breakpoints(self, 'container_max_heights')
        return super(CarouselPlugin, self).get_form(request, obj, **kwargs)

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(CarouselPlugin, cls).get_identifier(obj)
        num_cols = obj.get_children().count()
        content = ungettext_lazy('with {0} slide', 'with {0} slides',
                                 num_cols).format(num_cols)
        return format_html('{0}{1}', identifier, content)

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(CarouselPlugin, cls).get_css_classes(obj)
        if 'slide' in obj.glossary.get('options', []):
            css_classes.append('slide')
        return css_classes

    @classmethod
    def get_html_tag_attributes(cls, obj):
        attributes = super(CarouselPlugin, cls).get_html_tag_attributes(obj)
        attributes.update(cls.DEFAULT_CAROUSEL_ATTRIBUTES)
        attributes['data-interval'] = 1000 * int(
            obj.glossary.get('interval', 5))
        options = obj.glossary.get('options', [])
        attributes['data-pause'] = 'pause' in options and 'hover' or 'false'
        attributes['data-wrap'] = 'wrap' in options and 'true' or 'false'
        return attributes

    def save_model(self, request, obj, form, change):
        wanted_children = int(form.cleaned_data.get('num_children'))
        super(CarouselPlugin, self).save_model(request, obj, form, change)
        self.extend_children(obj, wanted_children, CarouselSlidePlugin)

    @classmethod
    def sanitize_model(cls, obj):
        sanitized = super(CarouselPlugin, cls).sanitize_model(obj)
        complete_glossary = obj.get_complete_glossary()
        # fill all invalid heights for this container to a meaningful value
        max_height = max(obj.glossary['container_max_heights'].values())
        pattern = re.compile(r'^(\d+)px$')
        for bp in complete_glossary['breakpoints']:
            if not pattern.match(obj.glossary['container_max_heights'].get(
                    bp, '')):
                obj.glossary['container_max_heights'][bp] = max_height
        return sanitized
예제 #11
0
class RecipeForm(forms.ModelForm):
    tags = forms.ModelMultipleChoiceField(
        queryset=Tag.objects.all(),
        widget=widgets.CheckboxSelectMultiple(),
    )
    amount = forms.MultipleChoiceField(required=False)
    food = forms.MultipleChoiceField(required=False)

    class Meta:
        model = Recipe
        fields = [
            "name",
            "description",
            "cooking_time",
            "tags",
            "image",
        ]
        labels = {
            "name": ("Название рецепта"),
            "description": ("Описание рецепта"),
            "cooking_time": ("Время приготовления"),
            "tags": ("Тэги"),
            "image": ("Вкуная картинка"),
            "food": ("Ингредиенты"),
            "amount": ("Количество ингредиента"),
        }
        help_text = {
            "name": ("Придуймайте название рецепта"),
            "description": ("Подробно опишите процесс приголовления"),
            "cooking_time": ("Сколько времени всё это займет в минутах"),
            "tags": ("К какой категории отностися этот рецепт"),
            "image": ("Загрузите какое-нибудь фото"),
            "food": ("Укажите нежные продукты"),
            "amount": ("Количество продуктов"),
        }

    def clean_food(self):
        food_names = self.data.getlist("nameIngredient")
        food_units = self.data.getlist("unitsIngredient")
        food_amount = self.data.getlist("valueIngredient")
        if len(food_names) != len(food_units) != len(food_amount):
            raise forms.ValidationError(
                "Number of titles does not equal number of units and "
                "amount of products")
        food = zip(food_names, food_units)
        cleaned_food = {}
        for count, (name, unit) in enumerate(food):
            if int(food_amount[count]) < 0:
                raise ValidationError("Количество не может быть меньше 0")
            ingredient = get_object_or_404(Food, name=name, unit=unit)
            cleaned_food[ingredient] = food_amount[count]
        self.cleaned_data["food"] = cleaned_food
        return self.cleaned_data["food"]

    def save(self, commit=True):
        super().save(commit=False)
        self.instance.author = self.initial["author"]
        self.instance.save()
        self.instance.tags.set(self.cleaned_data["tags"])
        ingredients = self.cleaned_data["food"]
        Ingredient.objects.filter(recipe=self.instance).exclude(
            food__in=ingredients.keys()).delete()
        for food, amount in ingredients.items():
            Ingredient.objects.update_or_create(recipe=self.instance,
                                                food=food,
                                                defaults={"amount": amount})
        return self.instance
예제 #12
0
class BootstrapPicturePlugin(LinkPluginBase):
    name = _("Picture")
    model_mixins = (
        ImagePropertyMixin,
        LinkElementMixin,
    )
    module = 'Bootstrap'
    parent_classes = ['BootstrapColumnPlugin', 'BootstrapPanelPlugin']
    require_parent = True
    allow_children = False
    raw_id_fields = ('image_file', )
    text_enabled = False
    admin_preview = False
    render_template = 'cascade/bootstrap3/linked-picture.html'
    default_css_class = 'img-responsive'
    default_css_attributes = ('image-shapes', )
    html_tag_attributes = {'image-title': 'title', 'alt-tag': 'tag'}
    fields = (
        'image_file',
        getattr(LinkPluginBase, 'glossary_field_map')['link'],
        'glossary',
    )
    LINK_TYPE_CHOICES = (('none', _("No Link")),) + \
        tuple(t for t in getattr(LinkForm, 'LINK_TYPE_CHOICES') if t[0] != 'email')
    RESIZE_OPTIONS = (
        ('upscale', _("Upscale image")),
        ('crop', _("Crop image")),
        ('subject_location', _("With subject location")),
        ('high_resolution', _("Optimized for Retina")),
    )
    glossary_fields = (
        PartialFormField(
            'image-title',
            widgets.TextInput(),
            label=_('Image Title'),
            help_text=
            _("Caption text added to the 'title' attribute of the <img> element."
              ),
        ),
        PartialFormField(
            'alt-tag',
            widgets.TextInput(),
            label=_('Alternative Description'),
            help_text=
            _("Textual description of the image added to the 'alt' tag of the <img> element."
              ),
        ),
    ) + getattr(LinkPluginBase, 'glossary_fields', ()) + (
        PartialFormField(
            'responsive-heights',
            MultipleCascadingSizeWidget(CASCADE_BREAKPOINTS_LIST,
                                        allowed_units=['px', '%'],
                                        required=False),
            label=_("Adapt Picture Heights"),
            initial={
                'xs': '100%',
                'sm': '100%',
                'md': '100%',
                'lg': '100%'
            },
            help_text=
            _("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints."
              ),
        ),
        PartialFormField(
            'responsive-zoom',
            MultipleCascadingSizeWidget(
                CASCADE_BREAKPOINTS_LIST, allowed_units=['%'], required=False),
            label=_("Adapt Picture Zoom"),
            initial={
                'xs': '0%',
                'sm': '0%',
                'md': '0%',
                'lg': '0%'
            },
            help_text=
            _("Magnification of picture in percent for distinct Bootstrap's breakpoints."
              ),
        ),
        PartialFormField(
            'resize-options',
            widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
            label=_("Resize Options"),
            help_text=_("Options to use when resizing the image."),
            initial=['subject_location', 'high_resolution']),
    )

    class Media:
        js = resolve_dependencies('cascade/js/admin/pictureplugin.js')

    def get_form(self, request, obj=None, **kwargs):
        utils.reduce_breakpoints(self, 'responsive-heights')
        image_file = ModelChoiceField(queryset=Image.objects.all(),
                                      required=False,
                                      label=_("Image"))
        Form = type(str('ImageForm'), (
            ImageFormMixin,
            getattr(LinkForm, 'get_form_class')(),
        ), {
            'LINK_TYPE_CHOICES': self.LINK_TYPE_CHOICES,
            'image_file': image_file
        })
        kwargs.update(form=Form)
        return super(BootstrapPicturePlugin,
                     self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        # image shall be rendered in a responsive context using the picture element
        elements = utils.get_picture_elements(context, instance)
        context.update({
            'is_responsive': True,
            'instance': instance,
            'placeholder': placeholder,
            'elements': elements,
        })
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(BootstrapPicturePlugin, cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapPicturePlugin, cls).get_identifier(obj)
        try:
            content = force_text(obj.image)
        except AttributeError:
            content = _("No Picture")
        return format_html('{0}{1}', identifier, content)
예제 #13
0
class BootstrapButtonMixin(IconPluginMixin):
    require_parent = True
    parent_classes = (
        'BootstrapColumnPlugin',
        'SimpleWrapperPlugin',
    )
    render_template = 'cascade/bootstrap3/button.html'
    allow_children = False
    default_css_class = 'btn'
    default_css_attributes = (
        'button_type',
        'button_size',
        'button_options',
        'quick_float',
    )
    ring_plugin = 'ButtonMixin'

    button_type = GlossaryField(
        ButtonTypeWidget.get_instance(),
        label=_("Button Type"),
        initial='btn-default',
        help_text=_("Display Link using this Button Style"))

    button_size = GlossaryField(
        ButtonSizeWidget.get_instance(),
        label=_("Button Size"),
        initial='',
        help_text=_("Display Link using this Button Size"))

    button_options = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=(
            ('btn-block', _('Block level')),
            ('disabled', _('Disabled')),
        )),
        label=_("Button Options"),
    )

    quick_float = GlossaryField(
        widgets.RadioSelect(choices=(
            ('', _("Do not float")),
            ('pull-left', _("Pull left")),
            ('pull-right', _("Pull right")),
        )),
        label=_("Quick Float"),
        initial='',
        help_text=_("Float the button to the left or right."))

    icon_align = GlossaryField(
        widgets.RadioSelect(choices=(
            ('', _("No Icon")),
            ('icon-left', _("Icon placed left")),
            ('icon-right', _("Icon placed right")),
        )),
        label=_("Icon alignment"),
        initial='',
        help_text=_("Add an Icon before or after the button content."))

    icon_font = GlossaryField(
        widgets.Select(),
        label=_("Font"),
    )

    symbol = GlossaryField(
        widgets.HiddenInput(),
        label=_("Select Symbol"),
    )

    class Media:
        js = ['cascade/js/admin/buttonmixin.js']

    def render(self, context, instance, placeholder):
        context = super(BootstrapButtonMixin,
                        self).render(context, instance, placeholder)
        try:
            icon_font = self.get_icon_font(instance)
            symbol = instance.glossary.get('symbol')
        except AttributeError:
            icon_font, symbol = None, None
        if icon_font and symbol:
            context['stylesheet_url'] = icon_font.get_stylesheet_url()
            mini_template = '{0}<i class="icon-{1} {2}" aria-hidden="true"></i>{3}'
            icon_align = instance.glossary.get('icon_align')
            if icon_align == 'icon-left':
                context['icon_left'] = format_html(mini_template, '', symbol,
                                                   'cascade-icon-left', ' ')
            elif icon_align == 'icon-right':
                context['icon_right'] = format_html(mini_template, ' ', symbol,
                                                    'cascade-icon-right', '')
        return context
예제 #14
0
class WorkStudyForm(_RegistrationStepFormBase, forms.ModelForm):
    class Meta:
        model = WorkStudyApplication
        fields = [
            'nickname_and_pronouns',
            'phone_number',
            'can_receive_texts_at_phone_number',
            'home_timezone',
            'other_timezone',
            'has_been_to_conclave',
            'has_done_work_study',
            'student_info',
            'job_preferences',
            'relevant_job_experience',
            'other_skills',
            'other_info',
        ]

        widgets = {
            'student_info':
            widgets.Textarea(attrs={
                'rows': 3,
                'cols': None
            }),
            'relevant_job_experience':
            widgets.Textarea(attrs={
                'rows': 5,
                'cols': None
            }),
            'other_skills':
            widgets.Textarea(attrs={
                'rows': 5,
                'cols': None
            }),
            'other_info':
            widgets.Textarea(attrs={
                'rows': 5,
                'cols': None
            }),
        }

        labels = dict(zip(fields, itertools.repeat('')))

    can_receive_texts_at_phone_number = YesNoRadioField(label='')

    has_been_to_conclave = YesNoRadioField(label='')
    has_done_work_study = YesNoRadioField(label='')

    home_timezone = ChoiceField(choices=(
        ('EDT', 'EDT (Eastern Daylight Time) UTC/GMT -4 hours -- '
         'this is CONCLAVE OFFICIAL TIME'),
        ('CDT', 'CDT (Central)'),
        ('MDT', 'MDT (Mountain)'),
        ('PDT',
         'PDT (Pacific Daylight Time) UTC/GMT -7 hours -- this is where Koren, '
         'the work-study coordinator, lives!'),
        ('other', 'Other'),
    ),
                                widget=widgets.RadioSelect)

    job_preferences = _PassThroughField(
        widget=widgets.CheckboxSelectMultiple(choices=WorkStudyJob.choices),
        label='')
예제 #15
0
class FilterableListForm(forms.Form):
    title_attrs = {'placeholder': 'Search for a specific word in item title'}
    topics_select_attrs = {
        'multiple': 'multiple',
        'data-placeholder': 'Search for topics',
    }
    authors_select_attrs = {
        'multiple': 'multiple',
        'data-placeholder': 'Search for authors'
    }
    from_select_attrs = {
        'class': 'js-filter_range-date js-filter_range-date__gte',
        'type': 'text',
        'placeholder': 'mm/dd/yyyy',
        'data-type': 'date'
    }
    to_select_attrs = from_select_attrs.copy()
    to_select_attrs.update({
        'class':
        'js-filter_range-date js-filter_range-date__lte',
    })

    title = forms.CharField(max_length=250,
                            required=False,
                            widget=widgets.TextInput(attrs=title_attrs))
    from_date = FilterDateField(
        required=False,
        input_formats=['%m/%d/%Y'],
        widget=widgets.DateInput(attrs=from_select_attrs))
    to_date = FilterDateField(required=False,
                              input_formats=['%m/%d/%Y'],
                              widget=widgets.DateInput(attrs=to_select_attrs))
    categories = forms.MultipleChoiceField(
        required=False,
        choices=ref.page_type_choices,
        widget=widgets.CheckboxSelectMultiple())
    topics = forms.MultipleChoiceField(
        required=False,
        choices=[],
        widget=widgets.SelectMultiple(attrs=topics_select_attrs))
    authors = forms.MultipleChoiceField(
        required=False,
        choices=[],
        widget=widgets.SelectMultiple(attrs=authors_select_attrs))

    def __init__(self, *args, **kwargs):
        parent = kwargs.pop('parent')
        hostname = kwargs.pop('hostname')
        super(FilterableListForm, self).__init__(*args, **kwargs)
        page_ids = CFGOVPage.objects.live_shared(hostname).descendant_of(
            parent).values_list('id', flat=True)
        self.set_topics(parent, page_ids, hostname)
        self.set_authors(parent, page_ids, hostname)

    # Populate Topics' choices
    def set_topics(self, parent, page_ids, hostname):
        tags = Tag.objects.filter(
            v1_cfgovtaggedpages_items__content_object__id__in=page_ids
        ).values_list('name', flat=True)

        # Orders by most to least common tags
        options = most_common(list(tags))
        most = [(option, option) for option in options[:3]]
        other = [(option, option) for option in options[3:]]
        self.fields['topics'].choices = \
            (('Most frequent', tuple(most)),
             ('All other topics', tuple(other)))

    # Populate Authors' choices
    def set_authors(self, parent, page_ids, hostname):
        authors = Tag.objects.filter(
            v1_cfgovauthoredpages_items__content_object__id__in=page_ids
        ).values_list('name', flat=True)

        # Orders by most to least common authors
        self.fields['authors'].choices = [
            (author, author) for author in most_common(list(authors))
        ]

    def clean(self):
        cleaned_data = super(FilterableListForm, self).clean()
        from_date = cleaned_data.get('from_date')
        to_date = cleaned_data.get('to_date')
        # Check if both date_lte and date_gte are present
        # If the 'start' date is after the 'end' date, swap them
        if (from_date and to_date) and to_date < from_date:
            data = dict(self.data)
            data_to_date = data['to_date']
            self.cleaned_data['to_date'], data['to_date'] = \
                from_date, data['from_date']
            self.cleaned_data['from_date'], data['from_date'] = \
                to_date, data_to_date
            self.data = data
        return self.cleaned_data

    # Does the job of {{ field }}
    # In the template, you pass the field and the id and name you'd like to
    # render the field with.
    def render_with_id(self, field, attr_id):
        for f in self.fields:
            if field.html_name == f:
                self.fields[f].widget.attrs.update({'id': attr_id})
                self.set_field_html_name(self.fields[f], attr_id)
                return self[f]

    # Sets the html name by replacing the render method to use the given name.
    def set_field_html_name(self, field, new_name):
        """
        This creates wrapper around the normal widget rendering,
        allowing for a custom field name (new_name).
        """
        old_render = field.widget.render
        if isinstance(field.widget, widgets.SelectMultiple):
            field.widget.render = lambda name, value, attrs=None, choices=(): \
                old_render(new_name, value, attrs, choices)
        else:
            field.widget.render = lambda name, value, attrs=None: \
                old_render(new_name, value, attrs)

    # Generates a query by iterating over the zipped collection of
    # tuples.
    def generate_query(self):
        final_query = Q()
        if self.is_bound:
            for query, field_name in zip(self.get_query_strings(),
                                         self.declared_fields):
                if self.cleaned_data.get(field_name):
                    final_query &= \
                        Q((query, self.cleaned_data.get(field_name)))
        return final_query

    # Returns a list of query strings to associate for each field, ordered by
    # the field declaration for the form. Note: THEY MUST BE ORDERED IN THE
    # SAME WAY AS THEY ARE DECLARED IN THE FORM DEFINITION.
    def get_query_strings(self):
        return [
            'title__icontains',  # title
            'date_published__gte',  # from_date
            'date_published__lte',  # to_date
            'categories__name__in',  # categories
            'tags__name__in',  # topics
            'authors__name__in',  # authors
        ]
예제 #16
0
class BootstrapImagePlugin(ImageAnnotationMixin, LinkPluginBase):
    name = _("Image")
    model_mixins = (
        ImagePropertyMixin,
        LinkElementMixin,
    )
    module = 'Bootstrap'
    parent_classes = ['BootstrapColumnPlugin']
    require_parent = True
    allow_children = False
    raw_id_fields = ['image_file']
    admin_preview = False
    ring_plugin = 'ImagePlugin'
    render_template = 'cascade/bootstrap4/linked-image.html'
    default_css_attributes = ['image_shapes', 'image_alignment']
    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}
    html_tag_attributes.update(LinkPluginBase.html_tag_attributes)
    fields = ['image_file'] + list(LinkPluginBase.fields)
    SHAPE_CHOICES = [('img-fluid', _("Responsive")), ('rounded', _('Rounded')),
                     ('rounded-circle', _('Circle')),
                     ('img-thumbnail', _('Thumbnail'))]
    RESIZE_OPTIONS = [('upscale', _("Upscale image")),
                      ('crop', _("Crop image")),
                      ('subject_location', _("With subject location")),
                      ('high_resolution', _("Optimized for Retina"))]
    ALIGNMENT_OPTIONS = [('float-left', _("Left")),
                         ('float-right', _("Right")), ('mx-auto', _("Center"))]

    image_shapes = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES),
        label=_("Image Shapes"),
        initial=['img-fluid'])

    image_width_responsive = GlossaryField(
        CascadingSizeWidget(allowed_units=['%'], required=False),
        label=_("Responsive Image Width"),
        initial='100%',
        help_text=_(
            "Set the image width in percent relative to containing element."),
    )

    image_width_fixed = GlossaryField(
        CascadingSizeWidget(allowed_units=['px'], required=False),
        label=_("Fixed Image Width"),
        help_text=_("Set a fixed image width in pixels."),
    )

    image_height = GlossaryField(
        CascadingSizeWidget(allowed_units=['px', '%'], required=False),
        label=_("Adapt Image Height"),
        help_text=
        _("Set a fixed height in pixels, or percent relative to the image width."
          ),
    )

    resize_options = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
        label=_("Resize Options"),
        help_text=_("Options to use when resizing the image."),
        initial=['subject_location', 'high_resolution'],
    )

    image_alignment = GlossaryField(
        widgets.RadioSelect(choices=ALIGNMENT_OPTIONS),
        label=_("Image Alignment"),
        help_text=_("How to align a non-responsive image."),
    )

    class Media:
        js = ['cascade/js/admin/imageplugin.js']

    def get_form(self, request, obj=None, **kwargs):
        LINK_TYPE_CHOICES = [('none', _("No Link"))]
        LINK_TYPE_CHOICES.extend(
            t for t in getattr(LinkForm, 'LINK_TYPE_CHOICES')
            if t[0] != 'email')
        image_file = ModelChoiceField(queryset=Image.objects.all(),
                                      required=False,
                                      label=_("Image"))
        Form = type(str('ImageForm'), (
            ImageFormMixin,
            getattr(LinkForm, 'get_form_class')(),
        ), {
            'LINK_TYPE_CHOICES': LINK_TYPE_CHOICES,
            'image_file': image_file
        })
        kwargs.update(form=Form)
        return super(BootstrapImagePlugin,
                     self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        try:
            tags = get_image_tags(instance)
        except Exception as exc:
            logger.warning(
                "Unable generate image tags. Reason: {}".format(exc))
        else:
            extra_styles = tags.pop('extra_styles')
            inline_styles = instance.glossary.get('inline_styles', {})
            inline_styles.update(extra_styles)
            instance.glossary['inline_styles'] = inline_styles
            context.update(
                dict(instance=instance, placeholder=placeholder, **tags))
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = cls.super(BootstrapImagePlugin, cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapImagePlugin, cls).get_identifier(obj)
        try:
            content = force_text(obj.image)
        except AttributeError:
            content = _("No Image")
        return format_html('{0}{1}', identifier, content)

    @classmethod
    def sanitize_model(cls, obj):
        sanitized = False
        parent = obj.parent
        while parent.plugin_type != 'BootstrapColumnPlugin':
            parent = parent.parent
            if parent is None:
                logger.warning(
                    "ImagePlugin(pk={}) has no ColumnPlugin as ancestor.".
                    format(obj.pk))
                return
        grid_column = parent.get_bound_plugin().get_grid_instance()
        min_max_bounds = grid_column.get_min_max_bounds()
        if obj.glossary.get('column_bounds') != min_max_bounds:
            obj.glossary['column_bounds'] = min_max_bounds
            sanitized = True
        obj.glossary.setdefault('media_queries', {})
        for bp in Breakpoint:
            media_query = '{} {:.2f}px'.format(bp.media_query,
                                               grid_column.get_bound(bp).max)
            if obj.glossary['media_queries'].get(bp.name) != media_query:
                obj.glossary['media_queries'][bp.name] = media_query
                sanitized = True
        return sanitized
예제 #17
0
class BootstrapButtonMixin(object):
    require_parent = True
    parent_classes = (
        'BootstrapColumnPlugin',
        'SimpleWrapperPlugin',
    )
    render_template = 'cascade/bootstrap3/button.html'
    allow_children = False
    text_enabled = True
    default_css_class = 'btn'
    default_css_attributes = (
        'button-type',
        'button-size',
        'button-options',
        'quick-float',
    )

    glossary_fields = (
        PartialFormField('button-type',
                         ButtonTypeRenderer.get_widget(),
                         label=_("Button Type"),
                         initial='btn-default',
                         help_text=_("Display Link using this Button Style")),
        PartialFormField('button-size',
                         ButtonSizeRenderer.get_widget(),
                         label=_("Button Size"),
                         initial='',
                         help_text=_("Display Link using this Button Size")),
        PartialFormField(
            'button-options',
            widgets.CheckboxSelectMultiple(choices=(
                ('btn-block', _('Block level')),
                ('disabled', _('Disabled')),
            )),
            label=_("Button Options"),
        ),
        PartialFormField(
            'quick-float',
            widgets.RadioSelect(choices=(
                ('', _("Do not float")),
                ('pull-left', _("Pull left")),
                ('pull-right', _("Pull right")),
            )),
            label=_("Quick Float"),
            initial='',
            help_text=_("Float the button to the left or right.")),
        PartialFormField(
            'icon-left',
            GlyphiconRenderer.get_widget(),
            label=_("Prepend icon"),
            initial='',
            help_text=_("Prepend a Glyphicon before the content.")),
        PartialFormField('icon-right',
                         GlyphiconRenderer.get_widget(),
                         label=_("Append icon"),
                         initial='',
                         help_text=_("Append a Glyphicon after the content.")),
    )

    def render(self, context, instance, placeholder):
        context = super(BootstrapButtonMixin,
                        self).render(context, instance, placeholder)
        mini_template = '{0}<span class="glyphicon glyphicon-{1} {2}" aria-hidden="true"></span>{3}'
        icon_left = instance.glossary.get('icon-left')
        if icon_left:
            context['icon_left'] = format_html(mini_template, '', icon_left,
                                               'cascade-icon-left', ' ')
        icon_right = instance.glossary.get('icon-right')
        if icon_right:
            context['icon_right'] = format_html(mini_template, ' ', icon_right,
                                                'cascade-icon-right', '')
        return context
예제 #18
0
class BootstrapJumbotronPlugin(BootstrapPluginBase):
    name = _("Jumbotron")
    model_mixins = (ImagePropertyMixin, ImageBackgroundMixin)
    form = JumbotronPluginForm
    default_css_class = 'jumbotron'
    parent_classes = ['BootstrapColumnPlugin']
    require_parent = False
    allow_children = True
    alien_child_classes = True
    raw_id_fields = ('image_file', )
    fields = (
        'glossary',
        'image_file',
    )
    render_template = 'cascade/bootstrap3/jumbotron.html'
    ATTACHMENT_CHOICES = ('scroll', 'fixed', 'local')
    VERTICAL_POSITION_CHOICES = ('top', '10%', '20%', '30%', '40%', 'center',
                                 '60%', '70%', '80%', '90%', 'bottom')
    HORIZONTAL_POSITION_CHOICES = ('left', '10%', '20%', '30%', '40%',
                                   'center', '60%', '70%', '80%', '90%',
                                   'right')
    REPEAT_CHOICES = ('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
    SIZE_CHOICES = ('auto', 'width/height', 'cover', 'contain')
    container_glossary_fields = (
        GlossaryField(
            widgets.CheckboxSelectMultiple(
                choices=get_widget_choices(),
                renderer=ContainerBreakpointsRenderer),
            label=_("Available Breakpoints"),
            name='breakpoints',
            initial=list(BS3_BREAKPOINT_KEYS)[::-1],
            help_text=_(
                "Supported display widths for Bootstrap's grid system.")),
        GlossaryField(
            MultipleCascadingSizeWidget(BS3_BREAKPOINT_KEYS,
                                        allowed_units=['px', '%'],
                                        required=False),
            label=_("Adapt Picture Heights"),
            name='container_max_heights',
            initial={
                'xs': '100%',
                'sm': '100%',
                'md': '100%',
                'lg': '100%'
            },
            help_text=
            _("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints."
              )),
        GlossaryField(widgets.CheckboxSelectMultiple(
            choices=BootstrapPicturePlugin.RESIZE_OPTIONS),
                      label=_("Resize Options"),
                      name='resize_options',
                      initial=['crop', 'subject_location', 'high_resolution'],
                      help_text=_("Options to use when resizing the image.")),
    )

    background_color = GlossaryField(
        ColorPickerWidget(),
        label=_("Background color"),
    )

    background_repeat = GlossaryField(
        widgets.RadioSelect(choices=[(c, c) for c in REPEAT_CHOICES]),
        initial='no-repeat',
        label=_("This property specifies how an image repeates."),
    )

    background_attachment = GlossaryField(
        widgets.RadioSelect(choices=[(c, c) for c in ATTACHMENT_CHOICES]),
        initial='local',
        label=
        _("This property specifies how to move the background relative to the viewport."
          ),
    )

    background_vertical_position = GlossaryField(
        widgets.Select(choices=[(c, c) for c in VERTICAL_POSITION_CHOICES]),
        initial='center',
        label=
        _("This property moves a background image vertically within its container."
          ),
    )

    background_horizontal_position = GlossaryField(
        widgets.Select(choices=[(c, c) for c in HORIZONTAL_POSITION_CHOICES]),
        initial='center',
        label=
        _("This property moves a background image horizontally within its container."
          ),
    )

    background_size = GlossaryField(
        widgets.RadioSelect(choices=[(c, c) for c in SIZE_CHOICES]),
        initial='auto',
        label=_("Background size"),
        help_text=_("This property specifies how an image is sized."),
    )

    background_width_height = GlossaryField(
        MultipleCascadingSizeWidget(['width', 'height'],
                                    allowed_units=['px', '%'],
                                    required=False),
        label=_("Background width and height"),
        help_text=
        _("This property specifies the width and height of a background image."
          ),
    )
    footnote_html = """
<p>For more information about the Jumbotron please read </p>
    """

    class Media:
        css = {'all': (settings.CMSPLUGIN_CASCADE['fontawesome_css_url'], )}
        js = resolve_dependencies('cascade/js/admin/jumbotronplugin.js')

    def get_form(self, request, obj=None, **kwargs):
        if self.get_parent_instance(request) is None:
            # we only ask for breakpoints, if the jumbotron is the root of the placeholder
            kwargs.update(glossary_fields=list(self.container_glossary_fields))
            kwargs['glossary_fields'].extend(self.glossary_fields)
        form = super(BootstrapJumbotronPlugin,
                     self).get_form(request, obj, **kwargs)
        return form

    def render(self, context, instance, placeholder):
        # image shall be rendered in a responsive context using the ``<picture>`` element
        elements = get_picture_elements(context, instance)
        context.update({
            'instance':
            instance,
            'placeholder':
            placeholder,
            'elements':
            [e for e in elements if 'media' in e] if elements else [],
        })
        return super(BootstrapJumbotronPlugin,
                     self).render(context, instance, placeholder)

    @classmethod
    def sanitize_model(cls, obj):
        # if the jumbotron is the root of the placeholder, we consider it as "fluid"
        obj.glossary['fluid'] = obj.parent is None
        sanitized = super(BootstrapJumbotronPlugin, cls).sanitize_model(obj)
        compute_media_queries(obj)
        return sanitized

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapJumbotronPlugin, cls).get_identifier(obj)
        try:
            content = obj.image.name or obj.image.original_filename
        except AttributeError:
            content = _("Without background image")
        return format_html('{0}{1}', identifier, content)
예제 #19
0
class FilterableListForm(forms.Form):
    title = forms.CharField(
        max_length=250,
        required=False,
        widget=forms.TextInput(attrs={
            'id': 'o-filterable-list-controls_title',
            'class': 'a-text-input a-text-input__full',
            'placeholder': 'Search for a specific word in item title',
        })
    )
    from_date = FilterableDateField(
        field_id='o-filterable-list-controls_from-date'
    )

    to_date = FilterableDateField(
        field_id='o-filterable-list-controls_to-date'
    )

    categories = forms.MultipleChoiceField(
        required=False,
        choices=ref.page_type_choices,
        widget=widgets.CheckboxSelectMultiple()
    )

    topics = forms.MultipleChoiceField(
        required=False,
        choices=[],
        widget=widgets.SelectMultiple(attrs={
            'id': 'o-filterable-list-controls_topics',
            'class': 'o-multiselect',
            'data-placeholder': 'Search for topics',
            'multiple': 'multiple',
        })
    )

    authors = forms.MultipleChoiceField(
        required=False,
        choices=[],
        widget=widgets.SelectMultiple(attrs={
            'id': 'o-filterable-list-controls_authors',
            'class': 'o-multiselect',
            'data-placeholder': 'Search for authors',
            'multiple': 'multiple',
        })
    )

    preferred_datetime_format = '%m/%d/%Y'

    def __init__(self, *args, **kwargs):
        self.filterable_pages = kwargs.pop('filterable_pages')
        self.wagtail_block = kwargs.pop('wagtail_block')
        super(FilterableListForm, self).__init__(*args, **kwargs)

        clean_categories(selected_categories=self.data.get('categories'))

        page_ids = self.filterable_pages.values_list('id', flat=True)
        self.set_topics(page_ids)
        self.set_authors(page_ids)

    def get_page_set(self):
        query = self.generate_query()
        return self.filterable_pages.filter(query).distinct().order_by(
            '-date_published'
        )

    def first_page_date(self):
        first_post = self.filterable_pages.order_by('date_published').first()
        if first_post:
            return first_post.date_published
        else:
            return date(2010, 1, 1)

    def prepare_options(self, arr):
        """
        Returns an ordered list of tuples of the format
        ('tag-slug-name', 'Tag Display Name')
        """
        arr = Counter(arr).most_common()  # Order by most to least common
        # Grab only the first tuple in the generated tuple,
        # which includes a count we do not need
        return [x[0] for x in arr]

    # Populate Topics' choices
    def set_topics(self, page_ids):
        if self.wagtail_block:
            self.fields['topics'].choices = self.wagtail_block.block \
                .get_filterable_topics(page_ids, self.wagtail_block.value)

    # Populate Authors' choices
    def set_authors(self, page_ids):
        authors = Tag.objects.filter(
            v1_cfgovauthoredpages_items__content_object__id__in=page_ids
        ).values_list('slug', 'name')
        options = self.prepare_options(arr=authors)

        self.fields['authors'].choices = options

    def clean(self):
        cleaned_data = super(FilterableListForm, self).clean()
        if self.errors.get('from_date') or self.errors.get('to_date'):
            return cleaned_data
        else:
            ordered_dates = self.order_from_and_to_date_filters(cleaned_data)
            transformed_dates = self.set_interpreted_date_values(ordered_dates)
            return transformed_dates

    def order_from_and_to_date_filters(self, cleaned_data):
        from_date = cleaned_data.get('from_date')
        to_date = cleaned_data.get('to_date')
        # Check if both date_lte and date_gte are present.
        # If the 'start' date is after the 'end' date, swap them.
        if (from_date and to_date) and to_date < from_date:
            data = dict(self.data)
            data_to_date = data['to_date']
            self.cleaned_data['to_date'], data['to_date'] = \
                from_date, data['from_date']
            self.cleaned_data['from_date'], data['from_date'] = \
                to_date, data_to_date
            self.data = data
        return self.cleaned_data

    def set_interpreted_date_values(self, cleaned_data):
        from_date = cleaned_data.get('from_date')
        to_date = cleaned_data.get('to_date')
        # If from_ or to_ is filled in, fill them both with sensible values.
        # If neither is filled in, leave them both blank.
        if from_date or to_date:
            if from_date:
                self.data['from_date'] = date.strftime(
                    cleaned_data['from_date'], self.preferred_datetime_format)
            else:
                # If there's a 'to_date' and no 'from_date',
                #  use date of earliest possible filter result as 'from_date'.
                earliest_results = self.first_page_date()
                cleaned_data['from_date'] = earliest_results
                self.data['from_date'] = date.strftime(
                    earliest_results, self.preferred_datetime_format)

            if to_date:
                transformed_to_date = end_of_time_period(
                    self.data['to_date'], cleaned_data['to_date'])
                cleaned_data['to_date'] = transformed_to_date
                self.data['to_date'] = date.strftime(
                    transformed_to_date, self.preferred_datetime_format)
            else:
                # If there's a 'from_date' but no 'to_date', use today's date.
                today = date.today()
                cleaned_data['to_date'] = today
                self.data['to_date'] = date.strftime(
                    today, self.preferred_datetime_format)

        return cleaned_data

    # Sets the html name by replacing the render method to use the given name.
    def set_field_html_name(self, field, new_name):
        """
        This creates wrapper around the normal widget rendering,
        allowing for a custom field name (new_name).
        """
        old_render = field.widget.render
        field.widget.render = lambda name, value, **kwargs: \
            old_render(new_name, value, **kwargs)

    # Generates a query by iterating over the zipped collection of
    # tuples.
    def generate_query(self):
        final_query = Q()
        if self.is_bound:
            for query, field_name in zip(
                self.get_query_strings(),
                self.declared_fields
            ):
                if self.cleaned_data.get(field_name):
                    final_query &= \
                        Q((query, self.cleaned_data.get(field_name)))
        return final_query

    # Returns a list of query strings to associate for each field, ordered by
    # the field declaration for the form. Note: THEY MUST BE ORDERED IN THE
    # SAME WAY AS THEY ARE DECLARED IN THE FORM DEFINITION.
    def get_query_strings(self):
        return [
            'title__icontains',      # title
            'date_published__gte',   # from_date
            'date_published__lte',   # to_date
            'categories__name__in',  # categories
            'tags__slug__in',        # topics
            'authors__slug__in',     # authors
        ]
예제 #20
0
class BootstrapPicturePlugin(ImageAnnotationMixin, LinkPluginBase):
    name = _("Picture")
    model_mixins = (
        ImagePropertyMixin,
        LinkElementMixin,
    )
    module = 'Bootstrap'
    parent_classes = ['BootstrapColumnPlugin', 'SimpleWrapperPlugin']
    require_parent = True
    allow_children = False
    raw_id_fields = ('image_file', )
    admin_preview = False
    ring_plugin = 'PicturePlugin'
    render_template = 'cascade/bootstrap4/linked-picture.html'
    default_css_class = 'img-fluid'
    default_css_attributes = ('image_shapes', )
    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}
    html_tag_attributes.update(LinkPluginBase.html_tag_attributes)
    fields = ['image_file'] + list(LinkPluginBase.fields)
    RESIZE_OPTIONS = [('upscale', _("Upscale image")),
                      ('crop', _("Crop image")),
                      ('subject_location', _("With subject location")),
                      ('high_resolution', _("Optimized for Retina"))]

    responsive_heights = GlossaryField(
        MultipleCascadingSizeWidget([bp.name for bp in Breakpoint],
                                    allowed_units=['px', '%'],
                                    required=False),
        label=_("Adapt Picture Heights"),
        initial={
            'xs': '100%',
            'sm': '100%',
            'md': '100%',
            'lg': '100%',
            'xl': '100%'
        },
        help_text=
        _("Heights of picture in percent or pixels for distinct Bootstrap's breakpoints."
          ),
    )

    responsive_zoom = GlossaryField(
        MultipleCascadingSizeWidget([bp.name for bp in Breakpoint],
                                    allowed_units=['%'],
                                    required=False),
        label=_("Adapt Picture Zoom"),
        initial={
            'xs': '0%',
            'sm': '0%',
            'md': '0%',
            'lg': '0%',
            'xl': '0%'
        },
        help_text=
        _("Magnification of picture in percent for distinct Bootstrap's breakpoints."
          ),
    )

    resize_options = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
        label=_("Resize Options"),
        help_text=_("Options to use when resizing the image."),
        initial=['subject_location', 'high_resolution'])

    class Media:
        js = ['cascade/js/admin/pictureplugin.js']

    def get_form(self, request, obj=None, **kwargs):
        LINK_TYPE_CHOICES = [('none', _("No Link"))]
        LINK_TYPE_CHOICES.extend(
            t for t in getattr(LinkForm, 'LINK_TYPE_CHOICES')
            if t[0] != 'email')
        image_file = ModelChoiceField(queryset=Image.objects.all(),
                                      required=False,
                                      label=_("Image"))
        Form = type(str('ImageForm'), (
            ImageFormMixin,
            getattr(LinkForm, 'get_form_class')(),
        ), {
            'LINK_TYPE_CHOICES': LINK_TYPE_CHOICES,
            'image_file': image_file
        })
        kwargs.update(form=Form)
        return super(BootstrapPicturePlugin,
                     self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        # image shall be rendered in a responsive context using the picture element
        try:
            elements = get_picture_elements(instance)
        except Exception as exc:
            logger.warning(
                "Unable generate picture elements. Reason: {}".format(exc))
        else:
            context.update({
                'instance': instance,
                'is_fluid': True,
                'placeholder': placeholder,
                'elements': elements,
            })
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = cls.super(BootstrapPicturePlugin,
                                cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapPicturePlugin, cls).get_identifier(obj)
        try:
            content = force_text(obj.image)
        except AttributeError:
            content = _("No Picture")
        return format_html('{0}{1}', identifier, content)

    @classmethod
    def sanitize_model(cls, obj):
        sanitized = False
        parent = obj.parent
        while parent.plugin_type != 'BootstrapColumnPlugin':
            parent = parent.parent
            if parent is None:
                logger.warning(
                    "PicturePlugin(pk={}) has no ColumnPlugin as ancestor.".
                    format(obj.pk))
                return
        grid_column = parent.get_bound_plugin().get_grid_instance()
        obj.glossary.setdefault('media_queries', {})
        for bp in Breakpoint:
            obj.glossary['media_queries'].setdefault(bp.name, {})
            width = round(grid_column.get_bound(bp).max)
            if obj.glossary['media_queries'][bp.name].get('width') != width:
                obj.glossary['media_queries'][bp.name]['width'] = width
                sanitized = True
            if obj.glossary['media_queries'][bp.name].get(
                    'media') != bp.media_query:
                obj.glossary['media_queries'][
                    bp.name]['media'] = bp.media_query
                sanitized = True
        return sanitized
예제 #21
0
class BootstrapContainerPlugin(BootstrapPluginBase):
    name = _("Container")
    require_parent = False
    form = BootstrapContainerForm
    WIDGET_CHOICES = (
        ('xs', _("Tiny (<{sm[0]}px)".format(**CASCADE_BREAKPOINTS_DICT))),
        ('sm', _("Small (≥{sm[0]}px and <{md[0]}px)".format(**CASCADE_BREAKPOINTS_DICT))),
        ('md', _("Medium (≥{md[0]}px and <{lg[0]}px)".format(**CASCADE_BREAKPOINTS_DICT))),
        ('lg', _("Large (≥{lg[0]}px)".format(**CASCADE_BREAKPOINTS_DICT))),
    )
    glossary_fields = (
        PartialFormField('breakpoints',
            widgets.CheckboxSelectMultiple(choices=WIDGET_CHOICES, renderer=ContainerBreakpointsRenderer),
            label=_('Available Breakpoints'),
            initial=['lg', 'md', 'sm', 'xs'],
            help_text=_("Supported display widths for Bootstrap's grid system.")
        ),
        PartialFormField('fluid',
            widgets.CheckboxInput(),
            label=_('Fluid Container'), initial=False,
            help_text=_("Changing your outermost '.container' to '.container-fluid'.")
        ),
    )
    glossary_variables = ['container_max_widths', 'media_queries']

    class Media:
        css = {'all': ('//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.min.css',)}
        js = ['cascade/js/admin/containerplugin.js']

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapContainerPlugin, cls).get_identifier(obj)
        breakpoints = obj.glossary.get('breakpoints')
        content = obj.glossary.get('fluid') and '(fluid) ' or ''
        if breakpoints:
            devices = ', '.join([force_text(CASCADE_BREAKPOINTS_DICT[bp][2]) for bp in breakpoints])
            content = _("{0}for {1}").format(content, devices)
        return format_html('{0}{1}', identifier, content)

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = super(BootstrapContainerPlugin, cls).get_css_classes(obj)
        if obj.glossary.get('fluid'):
            css_classes.append('container-fluid')
        else:
            css_classes.append('container')
        return css_classes

    def save_model(self, request, obj, form, change):
        super(BootstrapContainerPlugin, self).save_model(request, obj, form, change)
        obj.sanitize_children()

    @classmethod
    def sanitize_model(cls, obj):
        sanitized = super(BootstrapContainerPlugin, cls).sanitize_model(obj)
        parent_glossary = obj.get_parent_glossary()
        # compute the max width and the required media queries for each chosen breakpoint
        obj.glossary['container_max_widths'] = max_widths = {}
        obj.glossary['media_queries'] = media_queries = {}
        breakpoints = obj.glossary.get('breakpoints', [])
        last_index = len(breakpoints) - 1
        for index, bp in enumerate(breakpoints):
            try:
                max_widths[bp] = parent_glossary['container_max_widths'][bp]
            except KeyError:
                max_widths[bp] = CASCADE_BREAKPOINTS_DICT[bp][3]
            if last_index > 0:
                if index == 0:
                    next_bp = breakpoints[1]
                    media_queries[bp] = ['(max-width: {0}px)'.format(CASCADE_BREAKPOINTS_DICT[next_bp][0])]
                elif index == last_index:
                    media_queries[bp] = ['(min-width: {0}px)'.format(CASCADE_BREAKPOINTS_DICT[bp][0])]
                else:
                    next_bp = breakpoints[index + 1]
                    media_queries[bp] = ['(min-width: {0}px)'.format(CASCADE_BREAKPOINTS_DICT[bp][0]),
                                         '(max-width: {0}px)'.format(CASCADE_BREAKPOINTS_DICT[next_bp][0])]
        return sanitized
예제 #22
0
class BootstrapImagePlugin(ImageAnnotationMixin, LinkPluginBase):
    name = _("Image")
    model_mixins = (
        ImagePropertyMixin,
        LinkElementMixin,
    )
    module = 'Bootstrap'
    parent_classes = ('BootstrapColumnPlugin', )
    require_parent = True
    allow_children = False
    raw_id_fields = ('image_file', )
    admin_preview = False
    ring_plugin = 'ImagePlugin'
    render_template = 'cascade/bootstrap3/linked-image.html'
    default_css_attributes = ('image_shapes', )
    html_tag_attributes = {'image_title': 'title', 'alt_tag': 'tag'}
    fields = ('image_file', ) + LinkPluginBase.fields
    SHAPE_CHOICES = (
        ('img-responsive', _("Responsive")),
        ('img-rounded', _('Rounded')),
        ('img-circle', _('Circle')),
        ('img-thumbnail', _('Thumbnail')),
    )
    RESIZE_OPTIONS = (
        ('upscale', _("Upscale image")),
        ('crop', _("Crop image")),
        ('subject_location', _("With subject location")),
        ('high_resolution', _("Optimized for Retina")),
    )

    image_shapes = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=SHAPE_CHOICES),
        label=_("Image Shapes"),
        initial=['img-responsive'])

    image_width_responsive = GlossaryField(
        CascadingSizeWidget(allowed_units=['%'], required=False),
        label=_("Responsive Image Width"),
        initial='100%',
        help_text=_(
            "Set the image width in percent relative to containing element."),
    )

    image_width_fixed = GlossaryField(
        CascadingSizeWidget(allowed_units=['px'], required=False),
        label=_("Fixed Image Width"),
        help_text=_("Set a fixed image width in pixels."),
    )

    image_height = GlossaryField(
        CascadingSizeWidget(allowed_units=['px', '%'], required=False),
        label=_("Adapt Image Height"),
        help_text=
        _("Set a fixed height in pixels, or percent relative to the image width."
          ),
    )

    resize_options = GlossaryField(
        widgets.CheckboxSelectMultiple(choices=RESIZE_OPTIONS),
        label=_("Resize Options"),
        help_text=_("Options to use when resizing the image."),
        initial=['subject_location', 'high_resolution'])

    class Media:
        js = ['cascade/js/admin/imageplugin.js']

    def get_form(self, request, obj=None, **kwargs):
        utils.reduce_breakpoints(self,
                                 'responsive_heights',
                                 request=request,
                                 obj=obj)
        image_file = ModelChoiceField(queryset=Image.objects.all(),
                                      required=False,
                                      label=_("Image"))
        Form = type(
            str('ImageForm'), (
                ImageFormMixin,
                getattr(LinkForm, 'get_form_class')(),
            ), {
                'LINK_TYPE_CHOICES': ImageFormMixin.LINK_TYPE_CHOICES,
                'image_file': image_file
            })
        kwargs.update(form=Form)
        return super(BootstrapImagePlugin,
                     self).get_form(request, obj, **kwargs)

    def render(self, context, instance, placeholder):
        is_responsive = 'img-responsive' in instance.glossary.get(
            'image_shapes', [])
        options = dict(instance.get_complete_glossary(),
                       is_responsive=is_responsive)
        tags = utils.get_image_tags(context, instance, options)
        if tags:
            extra_styles = tags.pop('extra_styles')
            inline_styles = instance.glossary.get('inline_styles', {})
            inline_styles.update(extra_styles)
            instance.glossary['inline_styles'] = inline_styles
            context.update(
                dict(instance=instance, placeholder=placeholder, **tags))
        return context

    @classmethod
    def get_css_classes(cls, obj):
        css_classes = cls.super(BootstrapImagePlugin, cls).get_css_classes(obj)
        css_class = obj.glossary.get('css_class')
        if css_class:
            css_classes.append(css_class)
        return css_classes

    @classmethod
    def get_identifier(cls, obj):
        identifier = super(BootstrapImagePlugin, cls).get_identifier(obj)
        try:
            content = force_text(obj.image)
        except AttributeError:
            content = _("No Image")
        return format_html('{0}{1}', identifier, content)
예제 #23
0
class ArticleForm(Form):
    title = fields.CharField(
        max_length=128,
        required=True,
        label='标题(必填,128个字符以内)',
        widget=widgets.TextInput(attrs={"class": "form-control"}),
        error_messages={
            'invalid': '标题长度过长',
            'required': '标题不能为空'
        })
    summary = fields.CharField(max_length=255,
                               required=True,
                               label="摘要(必填,文章摘要,255个字符以内)",
                               widget=widgets.Textarea(attrs={
                                   "class": "form-control",
                                   "rows": 3
                               }),
                               error_messages={
                                   'invalid': '摘要长度过长',
                                   'required': '摘要不能为空'
                               })
    content = fields.CharField(required=False,
                               label="正文",
                               widget=widgets.Textarea(attrs={
                                   "class": "form-control kind-editor",
                                   "rows": 40
                               }))
    category_id = fields.ChoiceField(choices=[],
                                     required=False,
                                     label="文章分类",
                                     widget=widgets.RadioSelect(),
                                     error_messages={"invalid": "该分类不存在"})
    tag_id = fields.MultipleChoiceField(
        choices=[],
        required=False,
        label="文章标签",
        widget=widgets.CheckboxSelectMultiple(),
        error_messages={"invalid": "该标签不存在"})
    new_tag = fields.CharField(
        required=False,
        label="使用新的标签(多个关键字之间用英文“,”分隔,最多不超过10个)",
        widget=widgets.TextInput(attrs={"class": "form-control"}),
    )
    webplate_id = fields.ChoiceField(
        choices=[],
        required=False,
        label="网站分类",
        widget=widgets.RadioSelect(),
        error_messages={"invalid": "该网站分类不存在"},
    )

    def __init__(self, request, *args, **kwargs):
        """
        :param bid:     bid为blog账户的id
        :param aid:     aid为该blog下某篇文章的id,如果是添加文章aid=None, 如果是修改文章,aid不为None
        :param args:    其基类Form的位置参数
        :param kwargs:  其基类Form的关键字参数
        """
        super(ArticleForm, self).__init__(*args, **kwargs)
        self.bid = request.logged_blog.nid
        self.fields[
            'webplate_id'].choices = web_models.WebPlate.objects.values_list(
                'nid', 'title')
        self.fields[
            'category_id'].choices = web_models.Category.objects.filter(
                blog_id=self.bid).values_list('nid', 'title')
        self.fields['tag_id'].choices = web_models.Tag.objects.filter(
            blog_id=self.bid).values_list('nid', 'title')

    def clean_new_tag(self):
        """
            new_tag是用户新增的tag,要求以逗号分隔,不能为空,检查是否和用户已有的标签tag_dic存在重复:
                如果重复则不创建Tag类实例,但是将它添加到返回列表中
                如果不重复,且不为空,创建Tag类实例,同时将它添加到返回列表中
        :return: 返回列表,列表中的元素是需要和Article类实例创建对对多关系的。
        """
        data = self.cleaned_data.get("new_tag", "")
        new_tag_lst = data.strip().split(',')
        length = len(new_tag_lst)
        if not length:
            return []
        elif length > 10:
            raise ValidationError('新增便签长度超过10个')
        try:
            new_tag_set = set([nt.strip() for nt in new_tag_lst])
            tags = web_models.Tag.objects.filter(blog_id=self.bid).only(
                "title", 'nid')
            tag_dic = {tag.title: tag.nid for tag in tags}
            ret = []
            for nt in new_tag_set:
                if nt in tag_dic:
                    ret.append(tag_dic[nt])
                elif nt and nt not in tag_dic:  # 当标签不为空字符串,且数据库中没有该tag时,添加该tag
                    t = web_models.Tag.objects.create(title=nt,
                                                      blog_id=self.bid)
                    ret.append(t.nid)
            return ret
        except:
            raise ValidationError("添加的新标签有误,请修改后重试")
예제 #24
0
class TeacherModelForm(StarkModelForm):
    birthday = Ffields.DateField(
        required=False,
        label='生日',
        widget=Fwidgets.DateInput(attrs={
            'class': 'form-control',
            'type': 'date',
            'style': 'width: 600px'
        }))
    identity = form_models.ModelChoiceField(
        initial=1,
        empty_label=None,
        label='职务',
        queryset=teamodels.Identity.objects.all(),
        widget=Fwidgets.RadioSelect())
    course = form_models.ModelMultipleChoiceField(
        required=False,
        label='所带课程',
        queryset=scmodels.Course.objects.all(),
        widget=Fwidgets.CheckboxSelectMultiple())
    gender = Ffields.ChoiceField(required=False,
                                 label='性别',
                                 choices=((1, '男'), (2, '女')),
                                 widget=Fwidgets.RadioSelect())

    def __init__(self, *args, **kwargs):
        school_id = kwargs.pop('school_id')
        super(TeacherModelForm, self).__init__(*args, **kwargs)
        if school_id:
            self.school_id = school_id
            grade = form_models.ModelChoiceField(
                required=False,
                queryset=scmodels.Grade.objects.filter(
                    stuclass__school_id=school_id).distinct(),
                widget=Fwidgets.Select(attrs={
                    'class': 'form-control',
                    'id': 'grade'
                }),
                empty_label=None)
            self.fields['grade'] = grade

    class Meta:
        model = teamodels.TeacherInfo
        fields = ('last_name', 'first_name', 'gender', 'birthday', 'telephone',
                  'wechat', 'identity', 'course')
        widgets = {
            "last_name":
            Fwidgets.TextInput(attrs={
                'class': 'form-control',
                'style': 'width: 600px'
            }),
            "first_name":
            Fwidgets.TextInput(attrs={
                'class': 'form-control',
                'style': 'width: 600px'
            }),
            'wechat':
            Fwidgets.TextInput(attrs={
                'class': 'form-control',
                'style': 'width: 600px'
            }),
            'telephone':
            Fwidgets.TextInput(attrs={
                'class': 'form-control',
                'style': 'width: 600px'
            }),
        }
        error_messages = {
            "last_name": {
                "required": "请输入老师姓"
            },
            "first_name": {
                "required": "请输入老师名"
            },
            "gender": {
                "required": "请选择性别"
            },
        }
        labels = {
            'last_name': '老师姓(必填)',
            'first_name': '老师名(必填)',
        }

    def clean(self):
        '''
        校验该教师是否已经存在
        :return:
        '''
        birthday = self.cleaned_data.get('birthday')
        last_name = self.cleaned_data.get('last_name')
        first_name = self.cleaned_data.get('first_name')
        teacher_obj = teamodels.TeacherInfo.objects.filter(
            birthday=birthday,
            first_name=first_name,
            last_name=last_name,
            school=self.school_id)
        if teacher_obj:
            raise ValidationError('该教师已存在')
        return self.cleaned_data