class AppHookConfig(models.Model): """ This is the generic (abstract) model that holds the configurations for each AppHookConfig concrete model """ type = models.CharField( _('Type'), max_length=100, ) namespace = models.CharField( _('Instance namespace'), default=None, max_length=100, unique=True, ) app_data = AppDataField() cmsapp = None class Meta: verbose_name = _('Apphook config') verbose_name_plural = _('Apphook configs') unique_together = ('type', 'namespace') abstract = True def save(self, *args, **kwargs): self.type = '%s.%s' % (self.__class__.__module__, self.__class__.__name__) super(AppHookConfig, self).save(*args, **kwargs) def __str__(self): if self.cmsapp: return '%s / %s' % (self.cmsapp.name, self.namespace) else: return '%s / %s' % (self.type, self.namespace) def __getattr__(self, item): """ This allows to access config form attribute as normal model fields :param item: :return: """ try: return getattr(self.app_data.config, item) except Exception: raise AttributeError('attribute %s not found' % item)
class FlatComment(models.Model): site = SiteForeignKey(default=Site.objects.get_current) content_type = ContentTypeForeignKey() object_id = models.CharField(max_length=255) content_object = CachedGenericForeignKey('content_type', 'object_id') comment = models.TextField() submit_date = models.DateTimeField(default=None) user = CachedForeignKey(User) is_public = models.BooleanField(default=True) app_data = AppDataField() def _comment_list(self, reversed=False): if not hasattr(self, '__comment_list'): self.__comment_list = CommentList(self.content_type, self.object_id, reversed) return self.__comment_list def post(self, request=None): return self._comment_list().post_comment(self, request) def moderate(self, user=None, commit=True): return self._comment_list().moderate_comment(self, user, commit) def get_absolute_url(self, reversed=False): return '%s?p=%d' % (resolver.reverse( self.content_object, 'comments-list'), self._comment_list(reversed).page_index(self.pk)) def delete(self): self.moderate() super(FlatComment, self).delete() def save(self, **kwargs): if self.submit_date is None: self.submit_date = timezone.now() super(FlatComment, self).save(**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 Photo(models.Model): """ Represents original (unformated) photo uploaded by user. Used as source object for all the formatting stuff and to keep the metadata common to all related ``FormatedPhoto`` objects. """ title = models.CharField(_('Title'), max_length=200) description = models.TextField(_('Description'), blank=True) slug = models.SlugField(_('Slug'), max_length=255) # save it to YYYY/MM/DD structure image = models.ImageField(_('Image'), upload_to=upload_to, max_length=255, height_field='height', width_field='width') width = models.PositiveIntegerField(editable=False) height = models.PositiveIntegerField(editable=False) # important area important_top = models.PositiveIntegerField(null=True, blank=True) important_left = models.PositiveIntegerField(null=True, blank=True) important_bottom = models.PositiveIntegerField(null=True, blank=True) important_right = models.PositiveIntegerField(null=True, blank=True) # Authors and Sources authors = models.ManyToManyField(Author, verbose_name=_('Authors'), related_name='photo_set') source = models.ForeignKey(Source, blank=True, null=True, verbose_name=_('Source'), on_delete=models.SET_NULL) created = models.DateTimeField(auto_now_add=True) # generic JSON field to store app cpecific data app_data = AppDataField() class Meta: verbose_name = _('Photo') verbose_name_plural = _('Photos') def __unicode__(self): return self.title def get_absolute_url(self): return self.image.url def get_image_info(self): return { 'url': self.image.url, 'width': self.width, 'height': self.height, } def _get_image(self): if not hasattr(self, '_pil_image'): self.image.open() self._pil_image = Image.open(self.image) return self._pil_image def save(self, **kwargs): """Overrides models.Model.save. - Generates slug. - Saves image file. """ if not self.width or not self.height: self.width, self.height = self.image.width, self.image.height # prefill the slug with the ID, it requires double save if not self.id: img = self.image # store dummy values first... w, h = self.width, self.height self.image = '' self.width, self.height = w, h self.slug = '' super(Photo, self).save(force_insert=True) # ... so that we can generate the slug self.slug = str(self.id) + '-' + slugify(self.title) # truncate slug in order to fit in an ImageField and/or paths in Redirects self.slug = self.slug[:64] # .. tha will be used in the image's upload_to function self.image = img # and the image will be saved properly super(Photo, self).save(force_update=True) else: try: old = Photo.objects.get(pk=self.pk) force_update = True # delete formatedphotos if new image was uploaded if old.image != self.image: for f_photo in self.formatedphoto_set.all(): f_photo.delete() except Photo.DoesNotExist: # somebody is just trying to create new model with given PK force_update = False super(Photo, self).save(force_update=force_update) def ratio(self): "Return photo's width to height ratio" if self.height: return float(self.width) / self.height else: return None def get_formated_photo(self, format): "Return formated photo" return FormatedPhoto.objects.get_photo_in_format(self, format)
class Publishable(models.Model): app_data = AppDataField()
class Category(models.Model): app_data = AppDataField()
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 FlatComment(models.Model): site = SiteForeignKey(default=Site.objects.get_current) content_type = ContentTypeForeignKey() object_id = models.CharField(max_length=255) content_object = CachedGenericForeignKey('content_type', 'object_id') comment = models.TextField() submit_date = models.DateTimeField(default=None) user = CachedForeignKey(User) is_public = models.BooleanField(default=True) app_data = AppDataField() def _comment_list(self, reversed=False): if not hasattr(self, '__comment_list'): self.__comment_list = CommentList(self.content_type, self.object_id, reversed) return self.__comment_list def post(self, request=None): return self._comment_list().post_comment(self, request) def moderate(self, user=None, commit=True): return self._comment_list().moderate_comment(self, user, commit) def get_absolute_url(self, reversed=False): return '%s?p=%d' % ( resolver.reverse(self.content_object, 'comments-list'), self._comment_list(reversed).page_index(self.pk) ) def delete(self): self.moderate() super(FlatComment, self).delete() def save(self, **kwargs): if self.submit_date is None: self.submit_date = timezone.now() super(FlatComment, self).save(**kwargs) def has_edit_timer(self): '''has_edit_timer() -> bool ''' return EDIT_TIMER_ENABLED def is_edit_timer_expired(self): '''is_edit_timer_expired() -> bool Check whether the comment is still within the allowed edit time from the creation time. ''' age = datetime.now(self.submit_date.tzinfo) - self.submit_date if age >= timedelta(minutes=EDIT_TIMER_MINUTES): return True return False def get_remaining_edit_time(self): '''get_remaining_edit_time() -> str Returns the remaining edit time from comment creation. The returned string is formatted as HH:MM:SS, e.g. 0:01:23 ''' age = datetime.now(self.submit_date.tzinfo) - self.submit_date edit_time = timedelta(minutes=EDIT_TIMER_MINUTES) if age >= edit_time: return '0:00:00' seconds = edit_time.total_seconds() - age.total_seconds() remaining = timedelta(seconds=seconds) text = str(remaining) text = text.split('.')[0] return text
class AlternateRegistryModel(models.Model): alternate_registry = NamespaceRegistry() app_data = AppDataField(app_registry=alternate_registry)
class Author(models.Model): publishable = models.ForeignKey(Publishable, on_delete=models.CASCADE) app_data = AppDataField()
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 * * 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** TITLE """ return mark_safe((' ' * self.tree_path.count('/')) + self.title) draw_title.allow_tags = True
class Post(models.Model): title = models.CharField(_('Title'), max_length=255) slug = models.CharField( _('Slug'), max_length=255, unique=True, blank=True, help_text=_('Used in the URL. If changed, the URL will change. ' 'Clean it to have it re-created.')) language = models.CharField( _('language'), max_length=5, null=True, blank=True, choices=settings.LANGUAGES, help_text=_('leave empty to display in all languages')) key_visual = FilerImageField(verbose_name=_('Key Visual'), blank=True, null=True) lead_in = HTMLField( _('Lead-in'), help_text= _('Will be displayed in lists, and at the start of the detail page (in bold)' )) content = PlaceholderField('aldryn_blog_post_content', related_name='aldryn_blog_posts') author = models.ForeignKey(to=AUTH_USER_MODEL, verbose_name=_('Author')) coauthors = models.ManyToManyField(to=AUTH_USER_MODEL, verbose_name=_('Co-Authors'), null=True, blank=True, related_name='aldryn_blog_coauthors') publication_start = models.DateTimeField( _('Published Since'), default=timezone.now, help_text=_('Used in the URL. If changed, the URL will change.')) publication_end = models.DateTimeField(_('Published Until'), null=True, blank=True) category = models.ForeignKey(Category, verbose_name=_('Category'), null=True, blank=True) objects = RelatedManager() published = PublishedManager() tags = TaggableManager(blank=True) app_data = AppDataField() def __unicode__(self): return self.title def get_absolute_url(self): kwargs = { 'year': self.publication_start.year, 'month': self.publication_start.month, 'day': self.publication_start.day, 'slug': self.slug } if self.language and not getattr( settings, 'ALDRYN_BLOG_SHOW_ALL_LANGUAGES', False): with override(self.language): return reverse('aldryn_blog:post-detail', kwargs=kwargs) return reverse('aldryn_blog:post-detail', kwargs=kwargs) class Meta: ordering = ['-publication_start'] def save(self, **kwargs): if not self.slug: self.slug = slugify(self.title) return super(Post, self).save(**kwargs) def get_author_slug(self): # FIXME: This is a potential performance hogger return get_slug_for_user(self.author)
class Group(TranslationHelperMixin, TranslatedAutoSlugifyMixin, TranslatableModel): slug_source_field_name = 'name' translations = TranslatedFields( name=models.CharField(_('name'), max_length=255, help_text=_("Provide this group's name.")), description=HTMLField(_('description'), blank=True), slug=models.SlugField( _('slug'), max_length=255, default='', blank=True, help_text=_("Leave blank to auto-generate a unique slug.")), ) address = models.TextField( verbose_name=_('address'), blank=True) postal_code = models.CharField( verbose_name=_('postal code'), max_length=20, blank=True) city = models.CharField( verbose_name=_('city'), max_length=255, blank=True) phone = models.CharField( verbose_name=_('phone'), null=True, blank=True, max_length=100) fax = models.CharField( verbose_name=_('fax'), null=True, blank=True, max_length=100) email = models.EmailField( verbose_name=_('email'), blank=True, default='') website = models.URLField( verbose_name=_('website'), null=True, blank=True) sorting = models.PositiveSmallIntegerField( verbose_name=_('sorting field'), default=1, help_text=_('first with low value')) custom_fields_settings = JSONField(blank=True, null=True) custom_fields = JSONField(blank=True, null=True) #app config fields and mithods type = models.CharField( _('Type'), max_length=100, default='aldryn_people.Group', ) namespace = models.CharField( _('Instance namespace'), #default=None, max_length=100, unique=True, ) app_data = AppDataField() cmsapp = None def save(self, *args, **kwargs): self.type = '%s.%s' % ( self.__class__.__module__, self.__class__.__name__) if not self.namespace or self.namespace.startswith('202'): self.namespace = 'group-%s' % slugify(self.safe_translation_getter('name')) super(Group, self).save(*args, **kwargs) #def __str__(self): #if self.cmsapp: #return '%s / %s' % (self.cmsapp.name, self.namespace) #else: #return '%s / %s' % (self.type, self.namespace) #def __getattr__(self, item): #""" #This allows to access config form attribute as normal model fields #:param item: #:return: #""" #try: #return getattr(self.app_data.config, item) #except Exception: #raise AttributeError('attribute %s not found' % item) @property def company_name(self): warnings.warn( '"Group.company_name" has been refactored to "Group.name"', DeprecationWarning ) return self.safe_translation_getter('name') @property def company_description(self): warnings.warn( '"Group.company_description" has been refactored to ' '"Group.description"', DeprecationWarning ) return self.safe_translation_getter('description') class Meta: verbose_name = _('Group') verbose_name_plural = _('Groups') unique_together = ('type', 'namespace') def __str__(self): return self.safe_translation_getter( 'name', default=_('Group: {0}').format(self.pk)) def get_absolute_url(self, language=None): if not language: language = get_current_language() or get_default_language() slug, language = self.known_translation_getter( 'slug', None, language_code=language) if slug: kwargs = {'slug': slug} else: kwargs = {'pk': self.pk} with override(language): return reverse('%s:group-detail' % DEFAULT_APP_NAMESPACE, kwargs=kwargs) def get_public_url(self, language=None): return self.get_absolute_url(language)
class Author(models.Model): publishable = models.ForeignKey(Publishable) app_data = AppDataField()
class MyModel(models.Model): title = models.CharField(max_length=255, blank=True, default='') description = models.TextField(blank=True, default='') is_awesome = models.BooleanField(default=False) app_data = AppDataField()