示例#1
0
class RichTextImageContent(WithImage, models.Model):

    POSITION_CHOICES = (
        ('center', 'Center'),
        ('left', 'Left'),
        ('right', 'Right'),
    )

    template = 'cms/image_text_content.html'

    alt_text = models.CharField(_('Alternative Text'), max_length=255, blank=True, help_text=_('Description of the image'))
    text = RichTextField(_('Text'), blank=True)

    def render(self, **kwargs):
        templates = [self.template]
        return render_to_string(
            templates,
            {'content': self},
            context_instance=kwargs.get('context'),
        )

    @classmethod
    def initialize_type(cls, POSITION_CHOICES=POSITION_CHOICES, FORMAT_CHOICES=None):
        if POSITION_CHOICES:
            models.CharField('position',
                max_length=10,
                choices=POSITION_CHOICES,
                default=POSITION_CHOICES[0][0]
            ).contribute_to_class(cls, 'position')

    class Meta:
        abstract = True
        verbose_name = _('Rich text with image')
        verbose_name_plural = _('Rich texts with image')
 def initialize_type(cls, cleanse=None):
     cls.add_to_class(
         'text',
         RichTextField(_('text'), blank=True, cleanse=cleanse),
     )
示例#3
0
class SectionContent(models.Model):
    """
    Title, media file and rich text fields in one content block.
    """

    feincms_item_editor_inline = SectionContentInline
    feincms_item_editor_context_processors = (
        lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, )
    feincms_item_editor_includes = {
        'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE],
    }

    title = models.CharField(_('title'), max_length=200, blank=True)
    richtext = RichTextField(_('text'), blank=True)
    mediafile = MediaFileForeignKey(MediaFile,
                                    on_delete=models.CASCADE,
                                    verbose_name=_('media file'),
                                    related_name='+',
                                    blank=True,
                                    null=True)

    class Meta:
        abstract = True
        verbose_name = _('section')
        verbose_name_plural = _('sections')

    @classmethod
    def initialize_type(cls, TYPE_CHOICES=None, cleanse=None):
        if 'feincms.module.medialibrary' not in django_settings.INSTALLED_APPS:
            raise ImproperlyConfigured(
                'You have to add \'feincms.module.medialibrary\' to your'
                ' INSTALLED_APPS before creating a %s' % cls.__name__)

        if TYPE_CHOICES is None:
            raise ImproperlyConfigured(
                'You need to set TYPE_CHOICES when creating a'
                ' %s' % cls.__name__)

        cls.add_to_class(
            'type',
            models.CharField(_('type'),
                             max_length=10,
                             choices=TYPE_CHOICES,
                             default=TYPE_CHOICES[0][0]))

        if cleanse:
            cls.cleanse = cleanse

    @classmethod
    def get_queryset(cls, filter_args):
        # Explicitly add nullable FK mediafile to minimize the DB query count
        return cls.objects.select_related('parent',
                                          'mediafile').filter(filter_args)

    def render(self, **kwargs):
        if self.mediafile:
            mediafile_type = self.mediafile.type
        else:
            mediafile_type = 'nomedia'

        return ct_render_to_string(
            [
                'content/section/%s_%s.html' % (mediafile_type, self.type),
                'content/section/%s.html' % mediafile_type,
                'content/section/%s.html' % self.type,
                'content/section/default.html',
            ],
            {'content': self},
            request=kwargs.get('request'),
            context=kwargs.get('context'),
        )

    def save(self, *args, **kwargs):
        if getattr(self, 'cleanse', None):
            try:
                # Passes the rich text content as first argument because
                # the passed callable has been converted into a bound method
                self.richtext = self.cleanse(self.richtext)
            except TypeError:
                # Call the original callable, does not pass the rich richtext
                # content instance along
                self.richtext = self.cleanse.im_func(self.richtext)

        super(SectionContent, self).save(*args, **kwargs)

    save.alters_data = True
示例#4
0
文件: models.py 项目: thread/feincms
 def initialize_type(cls, cleanse=None):
     cls.add_to_class("text",
                      RichTextField(_("text"), blank=True, cleanse=cleanse))
示例#5
0
class RichTextContent(models.Model):
    """
    Rich text content. Uses TinyMCE by default, but can be configured to do
    anything you want using ``FEINCMS_RICHTEXT_INIT_CONTEXT`` and
    ``FEINCMS_RICHTEXT_INIT_TEMPLATE``.

    If you are using TinyMCE 4.x then ``FEINCMS_RICHTEXT_INIT_TEMPLATE`` 
    needs to be set to ``admin/content/richtext/init_tinymce4.html``.

    Optionally runs the HTML code through HTML cleaners if you specify
    ``cleanse=True`` when calling ``create_content_type``.
    """

    form = RichTextContentAdminForm
    feincms_item_editor_form = RichTextContentAdminForm

    feincms_item_editor_context_processors = (
        lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, )
    feincms_item_editor_includes = {
        'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE],
    }

    text = RichTextField(_('text'), blank=True)

    class Meta:
        abstract = True
        verbose_name = _('rich text')
        verbose_name_plural = _('rich texts')

    def render(self, **kwargs):
        return render_to_string('content/richtext/default.html',
                                {'content': self},
                                context_instance=kwargs.get('context'))

    def save(self, *args, **kwargs):
        # TODO: Move this to the form?
        if getattr(self, 'cleanse', False):
            # Passes the rich text content as first argument because
            # the passed callable has been converted into a bound method
            self.text = self.cleanse(self.text)

        super(RichTextContent, self).save(*args, **kwargs)

    save.alters_data = True

    @classmethod
    def initialize_type(cls, cleanse=False):
        def to_instance_method(func):
            def func_im(self, *args, **kwargs):
                return func(*args, **kwargs)

            return func_im

        if cleanse:
            # If cleanse is True use default cleanse method
            if cleanse == True:
                import warnings
                warnings.warn(
                    "Please pass a callable instead. cleanse=True is"
                    " being deprecated in favor of explicitly specifying the"
                    " cleansing function. To continue using the same"
                    " functionality, pip install feincms-cleanse and pass"
                    " cleanse=feincms_cleanse.cleanse_html to the"
                    " create_content_type call."
                    " Support for cleanse=True will be removed in FeinCMS v1.8.",
                    DeprecationWarning,
                    stacklevel=2)

                from feincms.utils.html.cleanse import cleanse_html
                cls.cleanse = to_instance_method(cleanse_html)
            # Otherwise use passed callable
            else:
                cls.cleanse = to_instance_method(cleanse)

        # TODO: Move this into somewhere more generic:
        if settings.FEINCMS_TIDY_HTML:
            # Make sure we can load the tidy function without dependency failures:
            try:
                get_object(settings.FEINCMS_TIDY_FUNCTION)
            except ImportError, e:
                raise ImproperlyConfigured(
                    "FEINCMS_TIDY_HTML is enabled but the HTML tidy function %s could not be imported: %s"
                    % (settings.FEINCMS_TIDY_FUNCTION, e))
示例#6
0
class Project(Base, TranslatedMixin):
    """ The heart of zipfelchappe. Projects are time limited and crowdfunded
        ideas that either get financed by reaching a minimum goal or not.
        Money will only be deducted from backers if the goal is reached. """

    max_duration = 120  # days

    def __init__(self, *args, **kwargs):
        # add the css and javascript files to project admin.
        super(Project, self).__init__(*args, **kwargs)
        self.feincms_item_editor_includes['head'].update([
            'admin/zipfelchappe/_project_head_include.html',
        ])

    title = models.CharField(_('title'), max_length=100)

    slug = models.SlugField(_('slug'), unique=True)

    position = models.IntegerField(_('ordering'))

    goal = CurrencyField(_('goal'),
                         max_digits=10,
                         decimal_places=2,
                         help_text=_('Amount to be raised.'))

    currency = models.CharField(_('currency'),
                                max_length=3,
                                choices=CURRENCY_CHOICES,
                                default=CURRENCY_CHOICES[0])

    start = models.DateTimeField(
        _('start'), help_text=_('Date when the project will be online.'))

    end = models.DateTimeField(_('end'),
                               help_text=_('End of the fundraising campaign.'))

    backers = models.ManyToManyField('Backer',
                                     verbose_name=_('backers'),
                                     through='Pledge')

    teaser_image = models.ImageField(_('image'),
                                     blank=True,
                                     null=True,
                                     upload_to=teaser_img_upload_to)

    teaser_text = RichTextField(_('text'), blank=True)

    objects = ProjectManager()

    class Meta:
        verbose_name = _('project')
        verbose_name_plural = _('projects')
        ordering = ('position', )
        get_latest_by = 'end'

    def save(self, *args, **kwargs):
        model = self.__class__

        if self.position is None:
            try:
                last = model.objects.order_by('-position')[0]
                self.position = last.position + 1
            except IndexError:
                self.position = 0

        return super(Project, self).save(*args, **kwargs)

    def __unicode__(self):
        return self.title

    def clean(self):
        if self.start and self.end and self.start > self.end:
            raise ValidationError(_('Start must be before end'))

        if self.start and self.end and \
           self.end - self.start > timedelta(days=self.max_duration):
            raise ValidationError(
                _('Project duration can be max. %(duration) days' %
                  {'duration': self.max_duration}))

        if self.pk:
            dbinst = Project.objects.get(pk=self.pk)

            if self.has_pledges and self.currency != dbinst.currency:
                raise ValidationError(
                    _('You cannot change the currency anymore'
                      ' once your project has been backed by users'))

            if self.has_pledges and self.end != dbinst.end:
                raise ValidationError(
                    _('You cannot change the end date anymore'
                      ' once your project has been backed by users'))

            if self.has_pledges and self.goal != dbinst.goal:
                raise ValidationError(
                    _('You cannot change the goal '
                      'once your project has been backed.'))

    @app_models.permalink
    def get_absolute_url(self):
        return 'zipfelchappe_project_detail', ROOT_URLS, (self.slug, )

    @classmethod
    def create_content_type(cls, model, *args, **kwargs):
        # Registers content type for translations too
        super(Project, cls).create_content_type(model, *args, **kwargs)
        if 'zipfelchappe.translations' in settings.INSTALLED_APPS:
            from .translations.models import ProjectTranslation
            kwargs['class_name'] = 'Translated%s' % model._meta.object_name
            ProjectTranslation.create_content_type(model, *args, **kwargs)

    @classmethod
    def register_regions(cls, *args, **kwargs):
        # Register regions for translations too
        super(Project, cls).register_regions(*args, **kwargs)
        if 'zipfelchappe.translations' in settings.INSTALLED_APPS:
            from .translations.models import ProjectTranslation
            ProjectTranslation.register_regions(*args, **kwargs)

    @cached_property
    def authorized_pledges(self):
        """
        Returns the Pledge instances which are autorized or paid.
        :return: Queryset of Pledges
        """
        return self.pledges.filter(status__gte=Pledge.AUTHORIZED)

    @cached_property
    def collectable_pledges(self):
        """
        Returns the Pledge instances which are autorized but not paid.
        :return: Queryset of Pledges
        """
        return self.pledges.filter(status=Pledge.AUTHORIZED)

    @cached_property
    def has_pledges(self):
        return self.pledges.count() > 0

    @cached_property
    def achieved(self):
        """
        Returns the amount of money raised
        :return: Amount raised
        """
        amount = self.authorized_pledges.aggregate(Sum('amount'))
        return amount['amount__sum'] or 0

    @property
    def percent(self):
        return int(round((self.achieved * 100) / self.goal))

    @property
    def goal_display(self):  # TODO: localize
        return u'%s %s' % (int(self.goal), self.currency)

    @property
    def achieved_display(self):
        return u'%d %s (%d%%)' % (self.achieved, self.currency, self.percent)

    @property
    def is_active(self):
        return self.start < now() < self.end

    @property
    def is_over(self):
        return now() > self.end

    @property
    def less_than_24_hours(self):
        warnings.warn('This property is deprecated and will be removed.',
                      DeprecationWarning,
                      stacklevel=2)
        return self.end - now() < timedelta(hours=24)

    @property
    def is_financed(self):
        return self.achieved >= self.goal

    @property
    def ended_successfully(self):
        return self.is_financed and self.is_over

    @cached_property
    def update_count(self):
        return self.updates.filter(status='published').count()

    @cached_property
    def public_pledges(self):
        return self.pledges.filter(status__gte=Pledge.AUTHORIZED,
                                   anonymously=False)

    def extraform(self):
        """ Returns additional form required to pledge to this project """
        fields = SortedDict()

        for field in self.extrafields.all():
            field.add_formfield(fields, self)

        return type(b'Form%s' % self.pk, (forms.Form, ), fields)
示例#7
0
class Update(CreateUpdateModel, TranslatedMixin):
    """ Updates are compareable to blog entries about a project """

    STATUS_DRAFT = 'draft'
    STATUS_PUBLISHED = 'published'

    STATUS_CHOICES = (
        (STATUS_DRAFT, _('Draft')),
        (STATUS_PUBLISHED, _('Published')),
    )

    project = models.ForeignKey('Project',
                                verbose_name=_('project'),
                                related_name='updates')

    title = models.CharField(_('title'), max_length=100)

    status = models.CharField(_('status'),
                              max_length=20,
                              choices=STATUS_CHOICES,
                              default='draft')
    mails_sent = models.BooleanField(editable=False, default=False)

    image = models.ImageField(_('image'),
                              blank=True,
                              null=True,
                              upload_to=update_upload_to)

    external = models.URLField(
        _('external content'),
        blank=True,
        null=True,
        help_text=_('Check http://embed.ly/providers for more details'),
    )

    content = RichTextField(_('content'), blank=True)

    attachment = models.FileField(_('attachment'),
                                  blank=True,
                                  null=True,
                                  upload_to=update_upload_to)

    class Meta:
        verbose_name = _('update')
        verbose_name_plural = _('updates')
        ordering = ('-created', )

    def __unicode__(self):
        return self.title

    @app_models.permalink
    def get_absolute_url(self):
        return ('zipfelchappe_update_detail', ROOT_URLS, (self.project.slug,
                                                          self.pk))

    @property
    def number(self):
        if hasattr(self, 'num'):
            return self.num
        updates = self.project.updates.filter(status=Update.STATUS_PUBLISHED)
        for index, item in enumerate(reversed(updates)):
            if item == self:
                self.num = index + 1
                return self.num
        return None
示例#8
0
class SectionContent(models.Model):
    """
    Title, media file and rich text fields in one content block.
    """

    feincms_item_editor_inline = SectionContentInline
    feincms_item_editor_context_processors = (
        lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT,
    )
    feincms_item_editor_includes = {
        'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE],
    }

    title = models.CharField(_('title'), max_length=200, blank=True)
    richtext = RichTextField(_('text'), blank=True)
    mediafile = MediaFileForeignKey(MediaFile, verbose_name=_('media file'),
        related_name='+', blank=True, null=True)

    class Meta:
        abstract = True
        verbose_name = _('section')
        verbose_name_plural = _('sections')

    @classmethod
    def initialize_type(cls, TYPE_CHOICES=None, cleanse=False):
        if 'feincms.module.medialibrary' not in django_settings.INSTALLED_APPS:
            raise ImproperlyConfigured, 'You have to add \'feincms.module.medialibrary\' to your INSTALLED_APPS before creating a %s' % cls.__name__

        if TYPE_CHOICES is None:
            raise ImproperlyConfigured, 'You need to set TYPE_CHOICES when creating a %s' % cls.__name__

        cls.add_to_class('type', models.CharField(_('type'),
            max_length=10, choices=TYPE_CHOICES,
            default=TYPE_CHOICES[0][0]))

        if cleanse:
            # If cleanse is True use default cleanse method
            if cleanse == True:
                import warnings
                warnings.warn("Please pass a callable instead. cleanse=True is"
                    " being deprecated in favor of explicitly specifying the"
                    " cleansing function. To continue using the same"
                    " functionality, pip install feincms-cleanse and pass"
                    " cleanse=feincms_cleanse.cleanse_html to the"
                    " create_content_type call."
                    " Support for cleanse=True will be removed in FeinCMS v1.8.",
                    DeprecationWarning, stacklevel=2)

                from feincms.utils.html.cleanse import cleanse_html
                cls.cleanse = cleanse_html
            # Otherwise use passed callable
            else:
                cls.cleanse = cleanse

    @classmethod
    def get_queryset(cls, filter_args):
        # Explicitly add nullable FK mediafile to minimize the DB query count
        return cls.objects.select_related('parent', 'mediafile').filter(filter_args)

    def render(self, **kwargs):
        if self.mediafile:
            mediafile_type = self.mediafile.type
        else:
            mediafile_type = 'nomedia'

        return render_to_string([
            'content/section/%s_%s.html' % (self.type, mediafile_type),
            'content/section/%s.html' % self.type,
            'content/section/%s.html' % mediafile_type,
            'content/section/default.html',
            ], {'content': self})

    def save(self, *args, **kwargs):
        if getattr(self, 'cleanse', False):
            try:
                # Passes the rich text content as first argument because
                # the passed callable has been converted into a bound method
                self.richtext = self.cleanse(self.text)
            except TypeError:
                # Call the original callable, does not pass the rich richtext
                # content instance along
                self.richtext = self.cleanse.im_func(self.text)

        super(SectionContent, self).save(*args, **kwargs)
    save.alters_data = True
示例#9
0
class RichTextContent(models.Model):
    """
    Rich text content. Uses TinyMCE by default, but can be configured to do
    anything you want using ``FEINCMS_RICHTEXT_INIT_CONTEXT`` and
    ``FEINCMS_RICHTEXT_INIT_TEMPLATE``.

    If you are using TinyMCE 4.x then ``FEINCMS_RICHTEXT_INIT_TEMPLATE``
    needs to be set to ``admin/content/richtext/init_tinymce4.html``.

    Optionally runs the HTML code through HTML cleaners if you specify
    ``cleanse=True`` when calling ``create_content_type``.
    """

    form = RichTextContentAdminForm
    feincms_item_editor_form = RichTextContentAdminForm

    feincms_item_editor_context_processors = (
        lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT,
    )
    feincms_item_editor_includes = {
        'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE],
    }

    text = RichTextField(_('text'), blank=True)

    class Meta:
        abstract = True
        verbose_name = _('rich text')
        verbose_name_plural = _('rich texts')

    def render(self, **kwargs):
        return render_to_string(
            'content/richtext/default.html',
            {'content': self},
            context_instance=kwargs.get('context'))

    def save(self, *args, **kwargs):
        # TODO: Move this to the form?
        if getattr(self, 'cleanse', None):
            # Passes the rich text content as first argument because
            # the passed callable has been converted into a bound method
            self.text = self.cleanse(self.text)

        super(RichTextContent, self).save(*args, **kwargs)
    save.alters_data = True

    @classmethod
    def initialize_type(cls, cleanse=None):
        def to_instance_method(func):
            def func_im(self, *args, **kwargs):
                return func(*args, **kwargs)
            return func_im

        if cleanse:
            cls.cleanse = to_instance_method(cleanse)

        # TODO: Move this into somewhere more generic:
        if settings.FEINCMS_TIDY_HTML:
            # Make sure we can load the tidy function without dependency
            # failures:
            try:
                get_object(settings.FEINCMS_TIDY_FUNCTION)
            except ImportError as e:
                raise ImproperlyConfigured(
                    "FEINCMS_TIDY_HTML is enabled but the HTML tidy function"
                    " %s could not be imported: %s" % (
                        settings.FEINCMS_TIDY_FUNCTION, e))