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), )
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
def initialize_type(cls, cleanse=None): cls.add_to_class("text", RichTextField(_("text"), blank=True, cleanse=cleanse))
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))
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)
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
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
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))