class Dependency(models.Model): """ Object dependency - model for recording dependent items. For example when we use photo in article content. """ target_ct = models.ForeignKey(ContentType, related_name='dependency_for_set') target_id = models.IntegerField() target = CachedGenericForeignKey('target_ct', 'target_id') dependent_ct = models.ForeignKey(ContentType, related_name='depends_on_set') dependent_id = models.IntegerField() dependent = CachedGenericForeignKey('dependent_ct', 'dependent_id') def __unicode__(self): return u'%s depends on %s' % (self.dependent, self.target) class Meta: app_label = 'core' verbose_name = _('Dependency') verbose_name_plural = _('Dependencies') ordering = ( 'dependent_ct', 'dependent_id', )
class Related(models.Model): """ Related objects - model for recording related ``Publishable`` objects. An example would be two articles sharing a similar topic. When something like this happens, a ``Related`` instance connecting the objects should be created. """ publishable = models.ForeignKey(Publishable, verbose_name=_('Publishable')) related_ct = ContentTypeForeignKey(verbose_name=_('Content type')) related_id = models.IntegerField(_('Object ID')) related = CachedGenericForeignKey('related_ct', 'related_id') objects = RelatedManager() class Meta: app_label = 'core' verbose_name = _('Related') verbose_name_plural = _('Related') def __unicode__(self): return _(u'%(pub)s relates to %(rel)s') % { 'pub': self.publishable, 'rel': self.related }
class Position(models.Model): " Represents a position on a page belonging to a certain category. " box_class = staticmethod(PositionBox) category = models.ForeignKey(Category, verbose_name=_('Category')) name = models.CharField(_('Name'), max_length=200) target_ct = models.ForeignKey(ContentType, 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') active_from = models.DateTimeField(_('Position active from'), null=True, blank=True) active_till = models.DateTimeField(_('Position active till'), null=True, blank=True) box_type = models.CharField(_('Box type'), max_length=200, blank=True) text = models.TextField(_('Definition'), blank=True) disabled = models.BooleanField(_('Disabled'), default=False) objects = PositionManager() 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, e: 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) b.prepare(context) return b.render()
class Dependency(models.Model): """ Captures relations between objects to simplify finding out what other objects my object depend on. This sounds mysterious, but the common use case is quite simple: keeping information which objects have been embedded in article content using **boxes** for example (these might be photos, galleries, ...). """ target_ct = ContentTypeForeignKey(related_name='dependency_for_set') target_id = models.IntegerField() target = CachedGenericForeignKey('target_ct', 'target_id') dependent_ct = ContentTypeForeignKey(related_name='depends_on_set') dependent_id = models.IntegerField() dependent = CachedGenericForeignKey('dependent_ct', 'dependent_id') class Meta: app_label = 'core' verbose_name = _('Dependency') verbose_name_plural = _('Dependencies') def __unicode__(self): return _(u'%(obj)s depends on %(dep)s') % {'obj': self.dependent, 'dep': self.target}
class TemplateBlock(models.Model): template = models.ForeignKey(DbTemplate) name = models.CharField(_('Name'), max_length=200) box_type = models.CharField(_('Box type'), max_length=200, blank=True) target_ct = models.ForeignKey(ContentType, null=True, blank=True) target_id = models.IntegerField(null=True, blank=True) active_from = models.DateTimeField(_('Block active from'), null=True, blank=True) active_till = models.DateTimeField(_('Block active till'), null=True, blank=True) text = models.TextField(_('Definition'), blank=True) objects = TemplateBlockManager() target = CachedGenericForeignKey(ct_field="target_ct", fk_field="target_id") def get_text(self): text = [] if self.box_type and self.target: text.append('{%% box %s for %s.%s with id %s %%}' % (self.box_type, self.target_ct.app_label, self.target_ct.model, self.target_id)) text.append(self.text.strip()) if self.box_type and self.target: text.append('{% endbox %}') return '\n'.join(text) class Meta: verbose_name = _('Template block') verbose_name_plural = _('Template blocks') # unique_together = (('template', 'name',),) unique_together = (( 'template', 'name', 'active_from', 'active_till', ), ) def __unicode__(self): return '%s' % self.name
class TotalRate(models.Model): """ save all rating for individual object. """ target_ct = models.ForeignKey(ContentType, db_index=True) target_id = models.PositiveIntegerField(_('Object ID'), db_index=True) target = CachedGenericForeignKey('target_ct', 'target_id') amount = models.DecimalField(_('Amount'), max_digits=10, decimal_places=2) objects = TotalRateManager() def __unicode__(self): return u'%s points for %s' % (self.amount, self.target) class Meta: verbose_name = _('Total rate') verbose_name_plural = _('Total rates')
class Related(models.Model): """ Related objects - model for recording related items. For example related articles. """ publishable = models.ForeignKey(Publishable, verbose_name=_('Publishable')) related_ct = models.ForeignKey(ContentType, verbose_name=_('Content type')) related_id = models.IntegerField(_('Object ID')) related = CachedGenericForeignKey('related_ct', 'related_id') objects = RelatedManager() def __unicode__(self): return u'%s relates to %s' % (self.publishable, self.related) class Meta: app_label = 'core' verbose_name = _('Related') verbose_name_plural = _('Related')
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)
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)