Ejemplo n.º 1
0
class AbstractLink(Illustrated, Logged):

    blog = fields.CachedForeignKey('blogs.Blog',
                                   related_name='links',
                                   verbose_name=_('Blog'))

    name = fields.CharField(
        max_length=63,
        verbose_name=_('Name'),
        help_text=_('Example: A framework for perfectionists'))
    url = models.URLField(
        max_length=127,
        verbose_name=_('Web Address'),
        help_text=
        _("Example: <code>http://www.djangoproject.com/</code> &mdash; don't forget the <code>http://</code>"
          ))
    rss = models.URLField(
        blank=True,
        max_length=127,
        verbose_name=_('RSS Address'),
        help_text=
        _("Example: <code>http://www.djangoproject.com/rss.xml</code> &mdash; don't forget the <code>http://</code>"
          ))
    description = fields.CharField(
        max_length=255,
        blank=True,
        verbose_name=_('Description'),
        help_text=
        _('This will be shown when someone hovers the link in the blogroll, or optionally below the link.'
          ))

    category = fields.CachedForeignKey('LinkCategory',
                                       blank=True,
                                       null=True,
                                       on_delete=models.PROTECT,
                                       related_name='links',
                                       verbose_name=_('Category'))

    class Meta:
        abstract = True
        folder_name = 'blog_links'
        ordering = ['name']
        verbose_name = _('Link')
        verbose_name_plural = _('Links')

    def __str__(self):
        return self.name

    def get_upload_path(self, filename):
        filename = slugify(self.name, ascii=True).replace('-', '_')
        return super(AbstractLink, self).get_upload_path(filename)
Ejemplo n.º 2
0
class AbstractCategory(Illustrated, Slugged, MetaData):

    blog = fields.CachedForeignKey('blogs.Blog',
                                   related_name='categories',
                                   verbose_name=_('Blog'))

    name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name'))
    description = fields.TextField(
        blank=True,
        verbose_name=_('Description'),
        help_text=_('The description is usually not prominent.'))

    class Meta:
        abstract = True
        folder_name = 'blog_categories'
        ordering = ['name']
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        kwargs = {'category_slug': self.slug}
        if settings.BLOG_MULTIPLE:
            kwargs['blog_slug'] = self.blog.slug
        return reverse('post_list', kwargs=kwargs)

    def get_feed_url(self):
        kwargs = {'category_slug': self.slug}
        if settings.BLOG_MULTIPLE:
            kwargs['blog_slug'] = self.blog.slug
        return full_reverse('post_feed', kwargs=kwargs)

    def get_upload_path(self, filename):
        filename = self.slug.replace('-', '_')
        return super(AbstractCategory, self).get_upload_path(filename)

    # GRAPPELLI SETTINGS

    @staticmethod
    def autocomplete_search_fields():
        return ('name__icontains', )
Ejemplo n.º 3
0
class AbstractLinkCategory(Slugged):

    blog = fields.CachedForeignKey('blogs.Blog',
                                   related_name='link_categories',
                                   verbose_name=_('Blog'))

    name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name'))
    description = fields.TextField(
        blank=True,
        verbose_name=_('Description'),
        help_text=_('The description is usually not prominent.'))

    objects = models.Manager()
    cache = LookupTable(indexed_fields=['slug'],
                        default_registry_key='links:DEFAULT_CATEGORY')

    class Meta:
        abstract = True
        ordering = ['name']
        verbose_name = _('Link Category')
        verbose_name_plural = _('Link Categories')

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        kwargs = {'link_category_slug': self.slug}
        if settings.BLOG_MULTIPLE:
            kwargs['blog_slug'] = self.blog.slug
        return reverse('link_list', kwargs=kwargs)

    # GRAPPELLI SETTINGS

    @staticmethod
    def autocomplete_search_fields():
        return ('name__icontains', )
Ejemplo n.º 4
0
class AbstractMessage(Logged):

    connection = fields.CachedForeignKey('Connection',
                                         on_delete=models.CASCADE,
                                         related_name='messages',
                                         verbose_name=_('Connection'))

    name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name'))

    sender_name = fields.CharField(max_length=127,
                                   verbose_name=_("Sender's Name"))
    sender_address = fields.CharField(max_length=127,
                                      verbose_name=_("Sender's Address"))
    recipient_name = fields.CharField(blank=True,
                                      max_length=255,
                                      verbose_name=_("Recipient's Name"))
    recipient_address = fields.CharField(
        blank=True,
        max_length=255,
        verbose_name=_("Recipient's Address"),
        help_text=_(
            'If field is blank, it will be populated when the message is sent.'
        ))
    reply_to_name = fields.CharField(blank=True,
                                     max_length=127,
                                     verbose_name=_("Reply To Name"))
    reply_to_address = fields.CharField(blank=True,
                                        max_length=127,
                                        verbose_name=_("Reply To Address"))

    subject = fields.CharField(max_length=255, verbose_name=_('Subject'))

    html = fields.TextField(verbose_name=_('HTML Version'))
    text = fields.TextField(blank=True, verbose_name=_('Plain Text Version'))

    class Meta:
        abstract = True
        ordering = ['name']
        verbose_name = _('Message')
        verbose_name_plural = _('Messages')

    def __str__(self):
        return self.name

    def save(self, **kwargs):
        if not self.text:
            self.text = extract_text(self.html)
        super(AbstractMessage, self).save(**kwargs)

    # CUSTOM METHODS

    def render(self, context=None):
        if not isinstance(context, Context):
            context = Context(context)

        email = EmailMultiAlternatives(
            Template(self.subject).render(context),
            Template(self.text).render(context),
            self.sender,
            self.recipient,
            connection=self.connection,
        )
        email.attach_alternative(
            Template(self.html).render(context),
            'text/html',
        )
        if self.reply_to_address:
            email.extra_headers['Reply-To'] = self.reply_to

        return email

    def render_and_send(self,
                        recipient_list,
                        reply_to=None,
                        context=None,
                        connection=None):
        to = []
        for recipient in recipient_list:
            if isinstance(recipient, (tuple, list)):
                name = recipient[0].strip()
                address = recipient[1].strip()
                if name:
                    to.append('"{0}" <{1}>'.format(name, address))
                else:
                    to.append(address)
            else:
                to.append(recipient)

        if isinstance(reply_to, (tuple, list)):
            name = reply_to[0].strip()
            address = reply_to[1].strip()
            if name:
                reply_to = '"{0}" <{1}>'.format(name, address)
            else:
                reply_to = address

        email = self.render(context)
        if not email.to:
            email.to = to
        if 'Reply-To' not in email.extra_headers and reply_to:
            email.extra_headers['Reply-To'] = reply_to
        if connection is not None:
            email.connection = connection
        email.send()

    # PROPERTIES

    @described_property(_('Recipient'), cached=True)
    def recipient(self):
        recipient_list = []
        if self.recipient_address.strip():
            name_list = self.recipient_name.split(',')
            address_list = self.recipient_address.split(',')
            for i, address in enumerate(address_list):
                address = address.strip()
                if len(name_list) > i:
                    name = name_list[i].strip()
                    recipient_list.append('"{0}" <{1}>'.format(name, address))
                else:
                    recipient_list.append(address)

        return recipient_list

    @described_property(_('Reply To'), cached=True)
    def reply_to(self):
        if self.reply_to_name:
            return '"{0}" <{1}>'.format(self.reply_to_name,
                                        self.reply_to_address)
        elif self.reply_to_address:
            return self.reply_to_address
        else:
            return None

    @described_property(_('Sender'), cached=True)
    def sender(self):
        if self.sender_name:
            return '"{0}" <{1}>'.format(self.sender_name, self.sender_address)
        elif self.sender_address:
            return self.sender_address
        else:
            return None

    # GRAPPELLI SETTINGS

    @staticmethod
    def autocomplete_search_fields():
        return ('name__icontains', 'subject__icontains')
Ejemplo n.º 5
0
class AbstractNewsletter(Orderable, Logged, Slugged, MetaData):
    """
    A regularly distributed publication to which subscribers can subscribe.
    """

    connection = fields.CachedForeignKey('emails.Connection',
                                         on_delete=models.CASCADE,
                                         related_name='newsletters',
                                         verbose_name=_('E-mail Connection'))

    guid = fields.GuidField(max_length=7,
                            editable=False,
                            unique=True,
                            verbose_name=_('Global Unique Identifier'))

    name = fields.CharField(unique=True, max_length=63, verbose_name=_('Name'))
    description = fields.RichTextField(blank=True,
                                       verbose_name=_('Description'))
    is_published = fields.BooleanField(default=True,
                                       verbose_name=_('Is Published?'))

    sender_name = fields.CharField(max_length=127,
                                   verbose_name=_("Sender's Name"))
    sender_address = fields.CharField(max_length=127,
                                      verbose_name=_("Sender's Address"))
    reply_to_name = fields.CharField(blank=True,
                                     max_length=127,
                                     verbose_name=_("Reply To Name"))
    reply_to_address = fields.CharField(blank=True,
                                        max_length=127,
                                        verbose_name=_("Reply To Address"))
    return_path_name = fields.CharField(blank=True,
                                        max_length=127,
                                        verbose_name=_("Return To Name"))
    return_path_address = fields.CharField(blank=True,
                                           max_length=127,
                                           verbose_name=_("Return To Address"))

    objects = NewsletterManager()
    cache = LookupTable(['guid', 'name'])

    class Meta:
        abstract = True
        verbose_name = _('Newsletter')
        verbose_name_plural = _('Newsletters')

    def __str__(self):
        return self.name

    @staticmethod
    def autocomplete_search_fields():
        return ('name__icontains', )

    # CUSTOM METHODS

    def get_default_meta_index(self):
        if self.is_published:
            return super(AbstractNewsletter, self).get_default_meta_index()
        else:
            return False

    # PROPERTIES

    @described_property(_('Reply To'))
    def reply_to(self):
        if self.reply_to_name:
            return '"{0}" <{1}>'.format(self.reply_to_name,
                                        self.reply_to_address)
        elif self.reply_to_address:
            return self.reply_to_address
        else:
            return None

    @described_property(_('Return Path'))
    def return_path(self):
        if self.return_path_name:
            return '"{0}" <{1}>'.format(self.return_path_name,
                                        self.return_path_address)
        elif self.return_path_address:
            return self.return_path_address
        else:
            return None

    @described_property(_('Sender'))
    def sender(self):
        if self.sender_name:
            return '"{0}" <{1}>'.format(self.sender_name, self.sender_address)
        elif self.sender_address:
            return self.sender_address
        else:
            return None
Ejemplo n.º 6
0
class AbstractPost(Illustrated, Displayable, Logged):

    blog = fields.CachedForeignKey('blogs.Blog',
                                   related_name='posts',
                                   verbose_name=_('Blog'))

    guid = fields.GuidField(editable=False,
                            verbose_name=_('Global Unique Identifier'))
    title = fields.CharField(max_length=255, verbose_name=_('Title'))
    subtitle = fields.CharField(
        blank=True,
        max_length=255,
        verbose_name=_('Subtitle'),
        help_text=_('Subtitles are optional complements of your title.'))
    excerpt = fields.RichTextField(
        blank=True,
        verbose_name=_('Excerpt'),
        help_text=_(
            'Excerpts are optional hand-crafted summaries of your content.'))
    content = fields.RichTextField(blank=True,
                                   processors=[attachment_tags],
                                   verbose_name=_('Content'))

    authors = models.ManyToManyField('authors.Author',
                                     limit_choices_to={'is_staff': True},
                                     related_name='posts',
                                     verbose_name=_('Authors'))
    categories = models.ManyToManyField('Category',
                                        blank=True,
                                        related_name='posts',
                                        verbose_name=_('Categories'))
    tags = models.ManyToManyField('Tag',
                                  blank=True,
                                  related_name='posts',
                                  verbose_name=_('Tags'))

    comment_status = fields.BooleanField(
        default=lambda: registry['posts:ALLOW_COMMENTS'],
        verbose_name=_('Allow comments on this post'))
    ping_status = fields.BooleanField(
        default=True,
        verbose_name=_('Allow trackbacks and pingbacks to this post'))

    views_count = models.PositiveIntegerField(default=0,
                                              blank=True,
                                              editable=False,
                                              db_index=True,
                                              verbose_name=_('Views'))
    comment_count = models.PositiveIntegerField(default=0,
                                                blank=True,
                                                editable=False,
                                                db_index=True,
                                                verbose_name=_('Comments'))
    ping_count = models.PositiveIntegerField(default=0,
                                             blank=True,
                                             editable=False,
                                             verbose_name=_('Pings'))

    score = models.PositiveIntegerField(default=0,
                                        blank=True,
                                        editable=False,
                                        db_index=True,
                                        verbose_name=_('Score'))

    objects = PostManager()

    search_fields = {'title': 5, 'subtitle': 3, 'content': 1}

    class Meta:
        abstract = True
        folder_name = 'blog_posts'
        index_together = [
            ('publish_status', 'creation_date'),
        ]
        ordering = ['-publish_from']
        verbose_name = _('Post')
        verbose_name_plural = _('Posts')

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        kwargs = {
            'post_slug': self.slug,
            'post_guid': self.guid,
        }
        if settings.BLOG_MULTIPLE:
            kwargs['blog_slug'] = self.blog.slug

        return reverse('post_detail', kwargs=kwargs)

    def get_feed_url(self):
        kwargs = {
            'post_slug': self.slug,
            'post_guid': self.guid,
        }
        if settings.BLOG_MULTIPLE:
            kwargs['blog_slug'] = self.blog.slug

        return full_reverse('comment_feed', kwargs=kwargs)

    # CUSTOM METHODS

    def allow_comments(self):
        if not self.comment_status or not self.is_published():
            return False
        limit = registry['comments:MAX_DAYS']
        if not limit:
            return True
        limit = timezone.now() - timedelta(limit)
        if self.publish_from:
            return (self.publish_from >= limit)
        elif self.creation_date:
            return (self.creation_date >= limit)
        else:
            return False

    allow_comments.boolean = True
    allow_comments.short_description = _('Allow comments?')

    def allow_pings(self):
        return (self.ping_status == True and self.is_published())

    allow_pings.boolean = True
    allow_pings.short_description = _('Allow pings?')

    def get_comments(self, limit=None, status=None, order_by=None):
        qs = self.comments.all()
        qs = qs.prefetch_related('author')
        if status is None:
            qs = qs.published()
        elif isinstance(status, six.string_types):
            qs = qs.filter(status__api_id=status)
        else:
            qs = qs.filter(status=status)
        if order_by is None:
            qs = qs.order_by('-creation_date')
        else:
            qs = qs.order_by(order_by)
        if limit:
            qs = qs[:limit]
        return qs

    def get_content(self):
        return mark_safe(self.content_html)

    get_content.short_description = _('Content')

    def get_excerpt(self, max_words=55, end_text='...'):
        if not self.excerpt_html:
            truncator = Truncator(self.content_html)
            if end_text == '...':
                end_text = '&hellip;'
            return mark_safe(truncator.words(max_words, end_text, html=True))
        else:
            return mark_safe(self.excerpt_html)

    get_excerpt.short_description = _('Excerpt')

    def get_next_in_order(self, user=None, same_blog=True):
        qs = self.__class__._default_manager.published(user)
        qs = qs.filter(publish_from__gt=self.publish_from)
        if same_blog:
            qs = qs.filter(blog_id=self.blog_id)
        qs = qs.order_by('publish_from')
        return qs.first()

    def get_previous_in_order(self, user=None, same_blog=True):
        qs = self.__class__._default_manager.published(user)
        qs = qs.filter(publish_from__lt=self.publish_from)
        if same_blog:
            qs = qs.filter(blog_id=self.blog_id)
        qs = qs.order_by('-publish_from')
        return qs.first()

    def get_publish_date(self):
        return self.publish_from if self.publish_from else FakeDate(0, 0, 0)

    get_publish_date.short_description = _('Publish Date')

    def get_related_posts(self, limit=5, same_blog=True):
        manager = self.__class__._default_manager
        qs = manager.published().exclude(pk__exact=self.pk)
        if same_blog:
            qs = qs.filter(blog_id=self.blog_id)

        def get_primary_keys(posts):
            if isinstance(posts, QuerySet):
                return list(posts.values_list('pk', flat=True))
            else:
                return [post.pk for post in posts]

        # Search similar posts using the post's title.
        query = ' '.join([
            term for term in [
                term.strip(punctuation)  # This transforms
                for term  # "Super toy: pack 10 u."
                in self.title.split()  # into
                if not term.endswith('.')  # "Super toy pack"
            ] if not term.isdigit()
        ])
        related_post_ids = get_primary_keys(list(qs.search(query)[:limit]))
        remaining = limit - len(related_post_ids)

        if remaining > 0:
            # Fetch post from post's categories.
            related_post_ids += get_primary_keys(
                qs.filter(categories__in=self.categories.all()).exclude(
                    pk__in=related_post_ids).
                distinct(  # Call to `distinct()` is required
                ).order_by(  # because `categories__in` filter
                    '-score'  # may result in duplicates.
                )[:remaining])
            remaining = limit - len(related_post_ids)

            if remaining > 0:
                # Fetch post from the rest of the blog.
                related_post_ids += get_primary_keys(
                    qs.exclude(pk__in=related_post_ids).order_by('-score')
                    [:remaining])
                remaining = limit - len(related_post_ids)

        related_posts = manager.filter(
            pk__in=related_post_ids).order_by('-score')
        return related_posts

    def increase_comment_count(self):
        record, _ = PostRecord.objects.get_or_create(post=self)
        record.comment_count = F('comment_count') + 1
        record.save(update_fields=['comment_count'])

    def increase_ping_count(self):
        record, _ = PostRecord.objects.get_or_create(post=self)
        record.ping_count = F('ping_count') + 1
        record.save(update_fields=['ping_count'])

    def increase_views_count(self):
        record, _ = PostRecord.objects.get_or_create(post=self)
        record.views_count = F('views_count') + 1
        record.save(update_fields=['views_count'])

    # GRAPPELLI SETTINGS

    @staticmethod
    def autocomplete_search_fields():
        return ('title__icontains', )
Ejemplo n.º 7
0
class BaseComment(Nestable, Logged, Linked):

    parent = ParentForeignKey('self',
                              blank=True,
                              null=True,
                              related_name='children',
                              verbose_name=_('Parent'))

    author_name = fields.CharField(blank=False,
                                   max_length=63,
                                   verbose_name=_('Name'))
    author_email = fields.EmailField(blank=False,
                                     max_length=63,
                                     verbose_name=_('Email Address'))
    author_url = models.URLField(blank=True,
                                 max_length=127,
                                 verbose_name=_('URL'))

    ip_address = models.GenericIPAddressField(blank=True,
                                              null=True,
                                              protocol='both',
                                              unpack_ipv4=True,
                                              verbose_name=_('IP Address'))
    user_agent = fields.TextField(blank=True, verbose_name=_('User Agent'))

    karma = fields.IntegerField(default=0, blank=True, verbose_name=_('Karma'))
    status = fields.CachedForeignKey('CommentStatus',
                                     on_delete=models.PROTECT,
                                     related_name='comments',
                                     verbose_name=_('Status'))
    is_published = fields.BooleanField(editable=False,
                                       default=True,
                                       verbose_name=_('Is Published?'))

    objects = CommentManager()

    class Meta:
        abstract = True
        index_together = [('status', 'creation_date')]
        ordering = ['-creation_date']
        permissions = [('can_moderate', _('Can moderate comments'))]
        verbose_name = _('Comment')
        verbose_name_plural = _('Comments')

    _author_email = Undefined
    _author_name = Undefined

    def __init__(self, *args, **kwargs):
        super(BaseComment, self).__init__(*args, **kwargs)
        self.old_status_id = self.status_id

    def __str__(self):
        args = (
            self.pk,
            self.get_author_name(),
        )
        return '#{0} {1}'.format(*args)

    def save(self, **kwargs):
        updated_fields = kwargs.get('update_fields', ())
        new_record = (kwargs.get('force_insert', False)
                      or not (self.pk or updated_fields))

        if (not updated_fields
                and (new_record or self.status_id != self.old_status_id)):
            status = self.get_status()
            self.is_published = status.publish_comment
            self.old_status_id = status.id

        self._author_email = Undefined
        self._author_name = Undefined
        super(BaseComment, self).save(**kwargs)

    def get_absolute_url(self):
        if not hasattr(self, 'post'):
            msg = ('{cls} is missing a post. Define {cls}.post, or override '
                   '{cls}.get_absolute_url().')
            raise ImproperlyConfigured(msg.format(cls=self.__class__.__name__))
        else:
            return '#'.join((self.post.get_absolute_url(), self.get_anchor()))

    # CUSTOM METHODS

    def get_anchor(self):
        return settings.COMMENT_ANCHOR_PATTERN.format(**self.get_anchor_data())

    def get_anchor_data(self):
        data = {
            'id': self.id,
            'parent_id': self.parent_id,
        }
        if hasattr(self, 'post_id'):
            data['post_id'] = self.post_id
        elif hasattr(self, 'post'):
            data['post_id'] = self.post.pk

        return data

    def get_author_email(self):
        if self._author_email is Undefined:
            if not hasattr(self, 'author') or self.author is None:
                self._author_email = self.author_email
            else:
                self._author_email = self.author.email

        return self._author_email

    get_author_email.short_description = _('Email Address')

    def get_author_link(self):
        url = self.get_author_url()
        name = conditional_escape(self.get_author_name())
        if url:
            return mark_safe('<a href="{0}" rel="nofollow">{1}</a>'.format(
                url, name))
        else:
            return mark_safe(name)

    get_author_link.short_description = _('Author')

    def get_author_name(self):
        if self._author_name is Undefined:
            if not hasattr(self, 'author') or self.author is None:
                self._author_name = self.author_name
            else:
                self._author_name = (self.author.get_full_name()
                                     or self.author.get_username())
        return self._author_name

    get_author_name.short_description = _('Name')

    def get_author_url(self):
        return self.author_url

    get_author_url.short_description = _('URL')

    def get_children(self, limit=None, status=None, order_by=None):
        qs = self.children.all()
        qs = qs.prefetch_related('author')
        if status is None:
            qs = qs.published()
        elif isinstance(status, six.string_types):
            qs = qs.filter(status__api_id=status)
        else:
            qs = qs.filter(status=status)

        if order_by is None:
            qs = qs.order_by('-creation_date')
        else:
            qs = qs.order_by(order_by)

        if limit:
            qs = qs[:limit]

        return qs

    def get_content(self):
        status = self.get_status()
        if status.comment_replacement:
            return mark_safe(status.comment_replacement)
        elif hasattr(self, 'content_html'):
            return mark_safe(self.content_html)
        elif hasattr(self, 'content'):
            return mark_safe(linebreaks(self.content, autoescape=True))
        else:
            msg = ('{cls} is missing a content. Define {cls}.content_html, '
                   '{cls}.content, or override {cls}.get_content().')
            raise ImproperlyConfigured(msg.format(cls=self.__class__.__name__))

    get_content.short_description = _('Content')

    def get_date(self):
        return self.creation_date

    get_date.short_description = _('Date')

    def get_excerpt(self, max_words=20, end_text='...'):
        content = self.get_content()
        if hasattr(content, '__html__'):
            # The __html__ attribute means the content was previously
            # marked as safe, so can include HTML tags.
            truncator = Truncator(content.__html__())
            if end_text == '...':
                end_text = '&hellip;'

            return mark_safe(truncator.words(max_words, end_text, html=True))
        else:
            return Truncator(content).words(max_words, end_text, html=False)

    get_excerpt.short_description = _('Excerpt')

    def get_link(self):
        url = self.get_absolute_url()
        text = ugettext('{author} on {post}').format(
            **{
                'author': self.get_author_name(),
                'post': self.get_post(),
            })
        return mark_safe('<a href="{0}">{1}</a>'.format(url, escape(text)))

    def get_status(self):
        return self.status

    get_excerpt.short_description = _('Status')