Beispiel #1
0
class Listing(models.Model):
    """
    Listing of an ``Publishable`` in a ``Category``. Each and every object that have it's
    own detail page must have a ``Listing`` object that is valid (not expired) and
    places it in the object's main category. Any object can be listed in any
    number of categories (but only once per category). Even if the object is
    listed in other categories besides its main category, its detail page's url
    still belongs to the main one.
    """
    box_class = staticmethod(ListingBox)

    publishable = CachedForeignKey(Publishable, verbose_name=_('Publishable'))
    category = CategoryForeignKey(verbose_name=_('Category'), db_index=True)

    publish_from = models.DateTimeField(_("Start of listing"), db_index=True)
    publish_to = models.DateTimeField(_("End of listing"),
                                      null=True,
                                      blank=True)

    commercial = models.BooleanField(
        _("Commercial"),
        default=False,
        help_text=_("Check this if the listing is of a commercial content."))

    objects = ListingManager()

    class Meta:
        app_label = 'core'
        verbose_name = _('Listing')
        verbose_name_plural = _('Listings')

    def __unicode__(self):
        try:
            return ugettext(u'%(pub)s listed in %(cat)s') % {
                'pub': self.publishable,
                'cat': self.category
            }
        except:
            return ugettext('Broken listing')

    def clean(self):
        if not self.publishable:
            return

        if self.publish_from and self.publish_from < self.publishable.publish_from:
            raise ValidationError(
                _('A publishable cannot be listed before it\'s published.'))

        if self.publishable.publish_to:
            if not self.publish_to or self.publish_to > self.publishable.publish_to:
                raise ValidationError(
                    _('A publishable cannot be listed longer than it\'s published.'
                      ))

    def get_absolute_url(self, domain=False):
        return self.publishable.get_absolute_url(domain)

    def get_domain_url(self):
        return self.get_absolute_url(domain=True)
Beispiel #2
0
class Publishable(models.Model):
    """
    Base class for all objects that can be published in Ella.
    """
    box_class = staticmethod(PublishableBox)

    content_type = ContentTypeForeignKey(editable=False)
    target = CachedGenericForeignKey('content_type', 'id')

    category = CategoryForeignKey(verbose_name=_('Category'))

    # Titles
    title = models.CharField(_('Title'), max_length=255)
    slug = models.SlugField(_('Slug'),
                            max_length=255,
                            validators=[validate_slug])

    # Authors and Sources
    authors = models.ManyToManyField(Author, verbose_name=_('Authors'))
    source = CachedForeignKey(Source,
                              blank=True,
                              null=True,
                              verbose_name=_('Source'),
                              on_delete=models.SET_NULL)

    # Main Photo
    photo = CachedForeignKey('photos.Photo',
                             blank=True,
                             null=True,
                             on_delete=models.SET_NULL,
                             verbose_name=_('Photo'))

    # Description
    description = models.TextField(_('Description'), blank=True)

    # Publish data
    published = models.BooleanField(_('Published'))
    publish_from = models.DateTimeField(
        _('Publish from'),
        default=core_settings.PUBLISH_FROM_WHEN_EMPTY,
        db_index=True)
    publish_to = models.DateTimeField(_("End of visibility"),
                                      null=True,
                                      blank=True)
    static = models.BooleanField(_('static'), default=False)

    # Last updated
    last_updated = models.DateTimeField(_('Last updated'), blank=True)

    # generic JSON field to store app cpecific data
    app_data = AppDataField(default='{}', editable=False)

    # has the content_published signal been sent for this instance?
    announced = models.BooleanField(help_text='Publish signal sent',
                                    default=False,
                                    editable=False)

    objects = PublishableManager()

    class Meta:
        app_label = 'core'
        verbose_name = _('Publishable object')
        verbose_name_plural = _('Publishable objects')

    def __unicode__(self):
        return self.title

    def __eq__(self, other):
        return isinstance(other, Publishable) and self.pk == other.pk

    def get_absolute_url(self, domain=False):
        " Get object's URL. "
        category = self.category

        kwargs = {
            'slug': self.slug,
        }

        if self.static:
            kwargs['id'] = self.pk
            if category.tree_parent_id:
                kwargs['category'] = category.tree_path
                url = reverse('static_detail', kwargs=kwargs)
            else:
                url = reverse('home_static_detail', kwargs=kwargs)
        else:
            publish_from = localize(self.publish_from)
            kwargs.update({
                'year': publish_from.year,
                'month': publish_from.month,
                'day': publish_from.day,
            })
            if category.tree_parent_id:
                kwargs['category'] = category.tree_path
                url = reverse('object_detail', kwargs=kwargs)
            else:
                url = reverse('home_object_detail', kwargs=kwargs)

        if category.site_id != settings.SITE_ID or domain:
            return 'http://' + category.site.domain + url
        return url

    def get_domain_url(self):
        return self.get_absolute_url(domain=True)

    def clean(self):
        if self.static or not self.published:
            return

        # fields are missing, validating uniqueness is pointless
        if not self.category_id or not self.publish_from or not self.slug:
            return

        qset = self.__class__.objects.filter(
            category=self.category,
            published=True,
            publish_from__day=self.publish_from.day,
            publish_from__month=self.publish_from.month,
            publish_from__year=self.publish_from.year,
            slug=self.slug)

        if self.pk:
            qset = qset.exclude(pk=self.pk)

        if qset:
            raise ValidationError(
                _('Another %s already published at this URL.') %
                self._meta.verbose_name)

    def save(self, **kwargs):
        # update the content_type if it isn't already set
        if not self.content_type_id:
            self.content_type = ContentType.objects.get_for_model(self)
        send_signal = None
        old_self = None
        if self.pk:
            try:
                old_self = self.__class__.objects.get(pk=self.pk)
            except Publishable.DoesNotExist:
                pass

        if old_self:
            old_path = old_self.get_absolute_url()
            new_path = self.get_absolute_url()

            # detect change in URL and not a static one
            if old_path != new_path and new_path and not old_self.static:
                # and create a redirect
                redirect = Redirect.objects.get_or_create(
                    old_path=old_path, site=self.category.site)[0]
                redirect.new_path = new_path
                redirect.save(force_update=True)
                # also update all potentially already existing redirects
                Redirect.objects.filter(new_path=old_path).exclude(
                    pk=redirect.pk).update(new_path=new_path)

            # detect change in publication status
            if old_self.is_published() != self.is_published():
                if self.is_published():
                    send_signal = content_published
                    self.announced = True
                else:
                    send_signal = content_unpublished
                    self.announced = False

            # @note: We also need to check for `published` flag even if both
            # old and new self `is_published()` method returns false.
            # This method can report false since we might be in time *before*
            # publication should take place but we still need to fire signal
            # that content has been unpublished.
            if old_self.published != self.published and self.published is False:
                send_signal = content_unpublished
                self.announced = False

            # changed publish_from and last_updated was default, change it too
            if old_self.last_updated == old_self.publish_from and self.last_updated == old_self.last_updated:
                self.last_updated = self.publish_from

            #TODO: shift Listing in case publish_(to|from) changes
        # published, send the proper signal
        elif self.is_published():
            send_signal = content_published
            self.announced = True

        if not self.last_updated:
            self.last_updated = self.publish_from

        super(Publishable, self).save(**kwargs)

        if send_signal:
            send_signal.send(sender=self.__class__, publishable=self)

    def delete(self):
        url = self.get_absolute_url()
        Redirect.objects.filter(new_path=url).delete()
        if self.announced:
            content_unpublished.send(sender=self.__class__, publishable=self)
        return super(Publishable, self).delete()

    def is_published(self):
        "Return True if the Publishable is currently active."
        cur_time = now()
        return self.published and cur_time > self.publish_from and \
            (self.publish_to is None or cur_time < self.publish_to)
Beispiel #3
0
class Category(models.Model):
    """
    ``Category`` is the **basic building block of Ella-based sites**. All the
    published content is divided into categories - every ``Publishable`` object
    has a ``ForeignKey`` to it's primary ``Category``. Primary category is then
    used to build up object's URL when using `Category.get_absolute_url` method.
    Besides that, objects can be published in other categories (aka "secondary"
    categories) via ``Listing``.

    Every site has exactly one root category (without a parent) that serve's as
    the sites's homepage.
    """
    template_choices = tuple((x, _(y)) for x, y in core_settings.CATEGORY_TEMPLATES)

    title = models.CharField(_("Title"), max_length=200)
    description = models.TextField(_("Description"), blank=True, help_text=_(
        'Description which can be used in link titles, syndication etc.'))
    content = models.TextField(_('Content'), default='', blank=True, help_text=_(
        'Optional content to use when rendering this category.'))
    template = models.CharField(_('Template'), max_length=100, help_text=_(
        'Template to use to render detail page of this category.'),
        choices=template_choices, default=template_choices[0][0])
    slug = models.SlugField(_('Slug'), max_length=255, validators=[category_slug_validator])
    tree_parent = CategoryForeignKey(null=True, blank=True,
        verbose_name=_("Parent category"))
    tree_path = models.CharField(verbose_name=_("Path from root category"),
        max_length=255, editable=False)
    site = SiteForeignKey()

    # generic JSON field to store app cpecific data
    app_data = AppDataField(_('Custom meta data'),
        help_text=_('If you need to define custom data for '
        'category objects, you can use this field to do so.'))

    objects = CategoryManager()

    class Meta:
        app_label = 'core'
        unique_together = (('site', 'tree_path'),)
        verbose_name = _('Category')
        verbose_name_plural = _('Categories')

    def __unicode__(self):
        return '%s/%s' % (self.site.name, self.tree_path)

    def save(self, **kwargs):
        "Override save() to construct tree_path based on the category's parent."
        old_tree_path = self.tree_path
        if self.tree_parent:
            if self.tree_parent.tree_path:
                self.tree_path = '%s/%s' % (self.tree_parent.tree_path, self.slug)
            else:
                self.tree_path = self.slug
        else:
            self.tree_path = ''
        Category.objects.clear_cache()
        super(Category, self).save(**kwargs)
        if old_tree_path != self.tree_path:
            # the tree_path has changed, update children
            children = Category.objects.filter(tree_parent=self)
            for child in children:
                child.save(force_update=True)

    def get_root_category(self):
        if '/' not in self.tree_path:
            return self
        path = self.tree_path.split('/')[0]
        return Category.objects.get_by_tree_path(path)

    def get_children(self, recursive=False):
        return Category.objects.get_children(self, recursive)

    @property
    def path(self):
        """
        Returns tree path of the category. Tree path is string that describes
        the whole path from the category root to the position of this category.

        @see: Category.tree_path
        """
        if self.tree_parent_id:
            return self.tree_path
        else:
            return self.slug

    def get_absolute_url(self):
        """
        Returns absolute URL for the category.
        """
        if not self.tree_parent_id:
            url = reverse('root_homepage')
        else:
            url = reverse('category_detail', kwargs={'category' : self.tree_path})
        if self.site_id != settings.SITE_ID:
            # prepend the domain if it doesn't match current Site
            return 'http://' + self.site.domain + url
        return url

    def draw_title(self):
        """
        Returns title indented by *&nbsp;* elements that can be used to show
        users a category tree.

        Examples:

        **Category with no direct parent (the category root)**
            TITLE

        **Category with one parent**
            &nsbp;TITLE

        **Category on third level of the tree**
            &nbsp;&nbsp;TITLE
        """
        return mark_safe(('&nbsp;&nbsp;' * self.tree_path.count('/')) + self.title)
    draw_title.allow_tags = True
Beispiel #4
0
class Position(models.Model):
    """
    Represents a position -- a placeholder -- on a page belonging to a certain
    category.
    """
    box_class = staticmethod(PositionBox)

    name = models.CharField(_('Name'), max_length=200)
    category = CategoryForeignKey(verbose_name=_('Category'))

    target_ct = ContentTypeForeignKey(verbose_name=_('Target content type'),
                                      null=True,
                                      blank=True)
    target_id = models.PositiveIntegerField(_('Target id'),
                                            null=True,
                                            blank=True)
    target = CachedGenericForeignKey('target_ct', 'target_id')
    text = models.TextField(_('Definition'), blank=True)
    box_type = models.CharField(_('Box type'), max_length=200, blank=True)

    active_from = models.DateTimeField(_('Position active from'),
                                       null=True,
                                       blank=True)
    active_till = models.DateTimeField(_('Position active till'),
                                       null=True,
                                       blank=True)
    disabled = models.BooleanField(_('Disabled'), default=False)

    objects = PositionManager()

    class Meta:
        verbose_name = _('Position')
        verbose_name_plural = _('Positions')

    def clean(self):
        if not self.category or not self.name:
            return

        if self.target_ct:
            try:
                get_cached_object(self.target_ct, pk=self.target_id)
            except self.target_ct.model_class().DoesNotExist:
                raise ValidationError(
                    _('This position doesn\'t point to a valid object.'))

        qset = Position.objects.filter(category=self.category, name=self.name)

        if self.pk:
            qset = qset.exclude(pk=self.pk)

        if self.active_from:
            qset = qset.exclude(active_till__lte=self.active_from)

        if self.active_till:
            qset = qset.exclude(active_from__gt=self.active_till)

        if qset.count():
            raise ValidationError(
                _('There already is a postion for %(cat)s named %(name)s fo this time.'
                  ) % {
                      'cat': self.category,
                      'name': self.name
                  })

    def __unicode__(self):
        return u'%s:%s' % (self.category, self.name)

    def render(self, context, nodelist, box_type):
        " Render the position. "
        if not self.target:
            if self.target_ct:
                # broken Generic FK:
                log.warning('Broken target for position with pk %r', self.pk)
                return ''
            try:
                return Template(self.text,
                                name="position-%s" % self.name).render(context)
            except TemplateSyntaxError:
                log.error('Broken definition for position with pk %r', self.pk)
                return ''

        if self.box_type:
            box_type = self.box_type
        if self.text:
            nodelist = Template('%s\n%s' % (nodelist.render({}), self.text),
                                name="position-%s" % self.name).nodelist

        b = self.box_class(self, box_type, nodelist)
        return b.render(context)