class Question(models.Model): SEPARATOR = '\x01' quiz = CachedForeignKey(Quiz) order = models.PositiveIntegerField() photo = CachedForeignKey(Photo, blank=True, null=True, on_delete=models.SET_NULL) text = models.TextField() choices_data = models.TextField() class Meta: unique_together = (( 'quiz', 'order', ), ) def clean(self): if len(self.choices) != self.quiz.choices: raise ValidationError('Number of choices must match the Quiz.') def get_choices(self): # TODO: maybe a SortedDict would be better? return map(lambda ch: Choice(*ch), enumerate(self.choices_data.split(self.SEPARATOR))) def set_choices(self, choices): self.choices_data = self.SEPARATOR.join( map(itemgetter(1), sorted(choices))) choices = property(get_choices, set_choices)
class Author(models.Model): """ Describes an Author of the published content. Author can be: * Human * Organization * ... All the fields except for ``slug`` are optional to enable maximum of flexibility. """ user = CachedForeignKey(User, verbose_name=_('User'), blank=True, null=True) name = models.CharField(_('Name'), max_length=200, blank=True) slug = models.SlugField(_('Slug'), max_length=255, unique=True, validators=[validate_slug]) description = models.TextField(_('Description'), blank=True) text = models.TextField(_('Text'), blank=True) email = models.EmailField(_('Email'), blank=True) photo = CachedForeignKey('photos.Photo', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('Photo')) class Meta: app_label = 'core' verbose_name = _('Author') verbose_name_plural = _('Authors') def __unicode__(self): if not self.name: return self.slug return self.name @models.permalink def get_absolute_url(self): return ('author_detail', [self.slug]) def recently_published(self, **kwargs): if core_settings.USE_REDIS_FOR_LISTINGS: return redis.AuthorListingHandler(self) root = Category.objects.get_by_tree_path('') return root.app_data.ella.get_listings(children=ListingHandler.ALL, author=self, **kwargs)
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)
class Author(models.Model): """ Describes an Author of the published content. Author can be: * Human * Organization * ... All the fields except for ``slug`` are optional to enable maximum of flexibility. """ user = CachedForeignKey(User, verbose_name=_('User'), blank=True, null=True) name = models.CharField(_('Name'), max_length=200, blank=True) slug = models.SlugField(_('Slug'), max_length=255, unique=True, validators=[validate_slug]) description = models.TextField(_('Description'), blank=True) text = models.TextField(_('Text'), blank=True) email = models.EmailField(_('Email'), blank=True) photo = CachedForeignKey('photos.Photo', blank=True, null=True, on_delete=models.SET_NULL, verbose_name=_('Photo')) class Meta: app_label = 'core' verbose_name = _('Author') verbose_name_plural = _('Authors') def __unicode__(self): return self.name @models.permalink def get_absolute_url(self): return ('author_detail', [self.slug]) def recently_published(self, **kwargs): Listing = get_model('core', 'Listing') return Listing.objects.get_listing(children=ListingHandler.ALL, publishable__authors__in=[self], **kwargs)
class Result(models.Model): quiz = CachedForeignKey(Quiz) choice = models.IntegerField() photo = CachedForeignKey(Photo, blank=True, null=True, on_delete=models.SET_NULL) title = models.CharField(max_length=200) text = models.TextField() # generic JSON field to store app specific data app_data = AppDataField(default='{}', editable=False) class Meta: unique_together = (( 'quiz', 'choice', ), ) def get_absolute_url(self): return resolver.reverse(self.quiz, 'quiz-result', choice=self.choice)
class CategoryLock(models.Model): category = CachedForeignKey(Category, verbose_name=_('Category'), unique=True) password = models.CharField(_('Password'), max_length=255) class Meta: verbose_name = _('Category Lock') verbose_name_plural = _('Category Locks') def __unicode__(self): return u'Locked Category %s' % self.category def form(self, *args, **kwargs): return CategoryLockForm(*args, **kwargs)
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)