class Queue(BaseModel): job_type = CharField(null=False, max_length=16, index=True) is_control = BooleanField(null=False, default=False, index=True) priority = IntegerField(default=9, index=True) data_string = TextField(null=True) data_integer = IntegerField(null=True, index=True) date_touched = DateTimeField(default=datetime.datetime.now) blog = ForeignKeyField(Blog, index=True, null=False) site = ForeignKeyField(Site, index=True, null=False)
class Media(BaseModel): filename = CharField(null=False) path = EnforcedCharField(unique=True) local_path = EnforcedCharField(unique=True, null=True) # deprecated? # should eventually be used to calculate where on the local filesystem # the file is when we are running in desktop mode, but I think we may # be able to deduce that from other things url = EnforcedCharField(unique=True, null=True) type = CharField(max_length=32, index=True) created_date = DateTimeField(default=datetime.datetime.now) modified_date = DateTimeField(default=datetime.datetime.now) friendly_name = TextField(null=True) tag_text = TextField(null=True) user = ForeignKeyField(User, null=False) blog = ForeignKeyField(Blog, null=True) site = ForeignKeyField(Site, null=True) @property def name(self): return self.friendly_name @property def link_format(self): return "{}/blog/{}/media/{}/edit".format(BASE_URL, str(self.blog.id), self.id) @property def preview_url(self): ''' In desktop mode, returns a preview_url otherwise, returns the mapped URL to the media as if it were being accessed through the site in question (assuming it can be found there) ''' if DESKTOP_MODE: return BASE_URL + "/media/" + str(self.id) else: return self.url @property def associated_with(self): ''' Returns a listing of all pages this media is associated with. ''' associated_with = MediaAssociation.select().where( MediaAssociation.media == self) return associated_with @property def preview_for_listing(self): return ''' <a href="media/{}/edit"><img style="max-height:50px" src="{}"></a>'''.format( self.id, self.preview_url)
class PageCategory(BaseModel): page = ForeignKeyField(Page, null=False, index=True) category = ForeignKeyField(Category, null=False, index=True) primary = BooleanField(default=True) @property def next_in_category(self): pass @property def previous_in_category(self): pass
class KeyValue(BaseModel): object = CharField(max_length=64, null=False, index=True) # table name objectid = IntegerField(null=True, index=True) key = EnforcedCharField(null=False, default="Key", index=True) value = TextField(null=True) parent = ForeignKeyField('self', null=True, index=True) is_schema = BooleanField(default=False) is_unique = BooleanField(default=False) value_type = CharField(max_length=64) def children(self, field=None, value=None): if self.is_schema is False: return None else: children = self.select().where(KeyValue.parent == self) if field is not None: children = children.select().where( getattr(KeyValue, field) == value) return children def siblings(self, field=None, value=None): if self.parent is None: return None else: siblings = self.select().where(KeyValue.parent == self.parent) if field is not None: siblings = siblings.select().where( getattr(KeyValue, field) == value) return siblings
class Tag(BaseModel): tag = TextField() blog = ForeignKeyField(Blog, null=False, index=True) is_hidden = BooleanField(default=False, index=True) @property def in_pages(self): tagged_pages = TagAssociation.select( TagAssociation.page).where(TagAssociation.tag == self).tuples() in_pages = Page.select().where(Page.id << tagged_pages) return in_pages @property def for_listing(self): template = tag_link_template.format(id=self.id, url=BASE_URL + "/blog/" + str(self.blog.id) + "/tag/" + str(self.id), tag=html_escape(self.tag)) return template @property def for_display(self): btn_type = 'warning' if self.tag[0] == "@" else 'info' template = tag_template.format( id=self.id, btn_type=btn_type, new='', tag=tag_link_template.format(id=self.id, url=BASE_URL + "/blog/" + str(self.blog.id) + "/tag/" + str(self.id), tag=html_escape(self.tag))) return template @property def new_tag_for_display(self): btn_type = 'warning' if self.tag[0] == "@" else 'info' template = tag_template.format( id=0, tag=new_tag_template.format(tag=html_escape(self.tag)), btn_type=btn_type, new='data-new-tag="{}" '.format(html_escape(self.tag))) return template
class Category(BaseModel): blog = ForeignKeyField(Blog, null=False, index=True) title = TextField() parent_category = IntegerField(default=None, null=True, index=True) default = BooleanField(default=False) @property def next_category(self): pass @property def previous_category(self): pass
class Page(BaseModel): title = TextField() type = IntegerField( default=0, index=True) # 0 = regular blog post; 1 = standalone page path = EnforcedCharField( unique=True, null=True) # only used if this is a standalone page external_path = EnforcedCharField( null=True, index=True) # used for linking in an external file basename = TextField() user = ForeignKeyField(User, null=False, index=True) text = TextField() excerpt = TextField(null=True) blog = ForeignKeyField(Blog, null=False, index=True) created_date = DateTimeField(default=datetime.datetime.now) modified_date = DateTimeField(null=True) publication_date = DateTimeField(null=True, index=True) status = CharField(max_length=32, index=True, default=page_status.unpublished) tag_text = TextField(null=True) currently_edited_by = IntegerField(null=True) author = user @property def status_id(self): return page_status.id[self.status] @property def link_format(self): return '{}/page/{}/edit'.format(BASE_URL, self.id) @property def listing_id(self): return 'page_title_{}'.format(self.id) @property def paginated_text(self): paginated_text = self.text.split('<!-- pagebreak -->') return paginated_text @property def tags(self): tag_list = Tag.select().where(Tag.id << TagAssociation.select( TagAssociation.tag).where(TagAssociation.page == self)).order_by( Tag.tag) return tag_list @property def author(self): return self.user @property def revisions(self): revisions = PageRevision.select().where( PageRevision.page_id == self.id).order_by( PageRevision.modified_date.desc()) return revisions @property def templates(self): ''' Returns all page templates for this page. ''' page_templates = Template.select().where( Template.blog == self.blog.id, Template.template_type == template_type.page) return page_templates @property def default_template(self): ''' Returns the default page template used by this blog. ''' default_template = self.templates.select().where( Template.default_type == archive_type.page).get() return default_template @property def archives(self): ''' Returns all date-based archives for this page. ''' page_templates = Template.select().where( Template.blog == self.blog.id, Template.template_type == template_type.archive) return page_templates @property def archive_mappings(self): ''' Returns mappings for all date-based archives for this page. ''' archive_mappings = TemplateMapping.select().where( TemplateMapping.template << self.archives.select( Template.id).tuples()) return archive_mappings @property def template_mappings(self): ''' Returns all template mappings associated with page for this blog. ''' template_mappings = TemplateMapping.select().where( TemplateMapping.template << self.templates.select( Template.id).tuples()) return template_mappings @property def default_template_mapping(self): ''' Returns the default template mapping associated with this page. ''' t = TemplateMapping.get( TemplateMapping.is_default == True, TemplateMapping.template << self.templates.select( Template.id).where(Template.default_type == "P").tuples()) default_template_mapping = self.publication_date.date().strftime( t.path_string) return default_template_mapping @property def fileinfos(self): ''' Returns any fileinfo objects associated with this page. ''' fileinfos = FileInfo.select().where(FileInfo.page == self) return fileinfos @property def default_fileinfo(self): ''' Returns the default fileinfo associated with the page. Useful if you have pages that have multiple mappings. ''' default_fileinfo = FileInfo.get( FileInfo.page == self, FileInfo.template_mapping == self.default_template.default_mapping) return default_fileinfo @property def permalink(self): if self.id is not None: f_info = self.default_fileinfo permalink = self.blog.url + "/" + f_info.file_path else: permalink = "" return permalink @property def preview_permalink(self): ''' TODO: the behavior of this function is wrong in both local and remote mode, it should specify a link to a temporary file generated from the current draft for the sake of a preview. ''' if self.status_id == page_status.published: if DESKTOP_MODE is True: tags = template_tags(page_id=self.id) preview_permalink = tpl( BASE_URL_ROOT + '/' + self.default_template_mapping + "." + self.blog.base_extension + "?_=" + str(self.blog.id), **tags.__dict__) else: preview_permalink = self.permalink else: preview_permalink = BASE_URL + "/page/" + str(self.id) + "/preview" return preview_permalink @property def categories(self): categories = PageCategory.select().where(PageCategory.page == self) return categories @property def primary_category(self): primary = self.categories.select().where( PageCategory.primary == True).get() return primary.category @property def media(self): ''' Returns iterable of all Media types associated with an entry. ''' media_association = MediaAssociation.select(MediaAssociation.id).where( MediaAssociation.page == self.id).tuples() media = Media.select().where(Media.id << media_association) return media @property def next_page(self): ''' Returns the next published page in the blog, in ascending chronological order. ''' try: next_page = self.blog.published_pages().select().where( Page.blog == self.blog, Page.publication_date > self.publication_date).order_by( Page.publication_date.asc(), Page.id.asc()).get() except Page.DoesNotExist: next_page = None return next_page @property def previous_page(self): ''' Returns the previous published page in the blog, in descending chronological order. ''' try: previous_page = self.blog.published_pages().select().where( Page.blog == self.blog, Page.publication_date < self.publication_date).order_by( Page.publication_date.desc(), Page.id.desc()).get() except Page.DoesNotExist: previous_page = None return previous_page @property def next_in_category(self): ''' This returns a dictionary of categories associated with the current entry along with the next entry in that category This way we can say self.next_in_category[category_id], etc. ''' pass @property def previous_in_category(self): pass def save(self, user, no_revision=False, backup_only=False, change_note=None): ''' Wrapper for the model's .save() action, which also updates the PageRevision table to include a copy of the current revision of the page BEFORE the save is committed. ''' from core.log import logger revision_save_result = None if no_revision == False and self.id is not None: page_revision = PageRevision.copy(self) revision_save_result = page_revision.save(user, self, False, change_note) page_save_result = Model.save(self) if backup_only is False else None if revision_save_result is not None: logger.info("Page {} edited by user {}.".format( self.for_log, user.for_log)) else: logger.info( "Page {} edited by user {} but without changes.".format( self.for_log, user.for_log)) return (page_save_result, revision_save_result) revision_fields = {'id': 'page_id'}
class Blog(SiteBase): site = ForeignKeyField(Site, null=False, index=True) theme = ForeignKeyField(Theme, null=True, index=True) @property def link_format(self): return "{}/blog/{}".format(BASE_URL, self.id) @property def media_path_(self, media_object=None): tags = template_tags(media=media_object, blog=self) template = tpl(self.media_path, **tags) # TODO: strip all newlines for a multi-line template? return template @property def users(self): blog_user_list = Permission.select(fn.Distinct(Permission.user)).where( Permission.site << [self.site, 0], Permission.blog << [self.id, 0]).tuples() blog_users = User.select().where(User.id << blog_user_list) return blog_users @property def max_revisions(self): return int(self.kv('MaxPageRevisions').value) @property def categories(self): ''' Lists all categories for this blog in their proper hierarchical order. ''' pass @property def permalink(self): return self.url def module(self, module_name): ''' Returns a module from the current blog that matches this name. If no module of the name is found, it will attempt to also import a template. ''' pass def pages(self, page_list=None): pages = Page.select( Page, PageCategory).where(Page.blog == self.id).join(PageCategory).where( PageCategory.primary == True).order_by( Page.publication_date.desc(), Page.id.desc()) if page_list is not None: pages = pages.select().where(Page.id << page_list) return pages def published_pages(self): published_pages = self.pages().select().where( Page.status == page_status.published) return published_pages def last_n_pages(self, count=0): ''' Returns the most recent pages posted in a blog, ordered by publication date. Set count to zero to retrieve all published pages. ''' last_n_pages = self.published_pages().select().order_by( Page.publication_date.desc()) if count > 0: last_n_pages = last_n_pages.select().limit(count) return last_n_pages def last_n_edited_pages(self, count=5): last_n_edited_pages = self.pages().select().order_by( Page.modified_date.desc()).limit(count) return last_n_edited_pages @property def index_file(self): return self.base_index + "." + self.base_extension @property def media(self): ''' Returns all Media types associated with a given blog. ''' media = Media.select().where(Media.blog == self) return media @property def templates(self): ''' Returns all templates associated with a given blog. ''' templates_in_blog = Template.select().where(Template.blog == self) return templates_in_blog def template(self, template_id): template_in_blog = Template.select().where(Template.blog == self, Template.id == template_id) return template_in_blog @property def index_templates(self): index_templates_in_blog = self.templates.select().where( Template.template_type == template_type.index) return index_templates_in_blog @property def archive(self, name): ''' Gets the entry link for the named archive template. ''' archive = self.templates.select().where( Template.title == name, Template.template_type == template_type.archive) archive.default_mapping.fileinfos @property def archive_templates(self): archive_templates_in_blog = self.templates.select().where( Template.template_type == template_type.archive) return archive_templates_in_blog @property def template_mappings(self): ''' Returns all template mappings associated with a given blog. ''' template_mappings_in_blog = TemplateMapping.select().where( TemplateMapping.id << self.templates.select(Template.id)) return template_mappings_in_blog @property def fileinfos(self): ''' Returns all fileinfos associated with a given blog. ''' fileinfos_for_blog = FileInfo.select().where( FileInfo.template_mapping << self.template_mappings.select( TemplateMapping.id)) return fileinfos_for_blog
class Permission(BaseModel): user = ForeignKeyField(User, index=True) permission = IntegerField(null=False) blog = ForeignKeyField(Blog, index=True, null=True) site = ForeignKeyField(Site, index=True, null=True)
class FileInfoContext(BaseModel): fileinfo = ForeignKeyField(FileInfo, null=False, index=True) object = CharField(max_length=1) ref = IntegerField(null=True)
class FileInfo(BaseModel): page = ForeignKeyField(Page, null=True, index=True) template_mapping = ForeignKeyField(TemplateMapping, null=False, index=True) file_path = EnforcedCharField(null=False) sitewide_file_path = EnforcedCharField(index=True, null=False, unique=True) url = EnforcedCharField(null=False, index=True, unique=True) modified_date = DateTimeField(default=datetime.datetime.now) mapping_sort = EnforcedCharField(null=True, default=None, index=True) @property def xref(self): xref = TemplateMapping.select().where( TemplateMapping.id == self.template_mapping).get() return xref @property def author(self): try: author = self.context.select().where( FileInfoContext.object == "A").get() author = author.ref except FileInfoContext.DoesNotExist: author = None return author @property def context(self): context = FileInfoContext.select().where( FileInfoContext.fileinfo == self).order_by( FileInfoContext.id.asc()) return context @property def year(self): try: year = self.context.select().where( FileInfoContext.object == "Y").get() year = year.ref except FileInfoContext.DoesNotExist: year = None return year @property def month(self): try: month = self.context.select().where( FileInfoContext.object == "M").get() month = month.ref except FileInfoContext.DoesNotExist: month = None return month @property def category(self): try: category = self.context.select().where( FileInfoContext.object == "C").get() category = category.ref except FileInfoContext.DoesNotExist: category = None return category
class TagAssociation(BaseModel): tag = ForeignKeyField(Tag, null=False, index=True) page = ForeignKeyField(Page, null=True, index=True) media = ForeignKeyField(Media, null=True, index=True)
class MediaAssociation(BaseModel): media = ForeignKeyField(Media) page = ForeignKeyField(Page, null=True) blog = ForeignKeyField(Blog, null=True) site = ForeignKeyField(Site, null=True)
class TemplateMapping(BaseModel): template = ForeignKeyField(Template, null=False, index=True) is_default = BooleanField(default=False, null=True) path_string = TextField() # archive_type = IntegerField() # 1 = Index # 2 = Page # 3 = Date-Based # TODO: I believe this was deprecated a long time ago archive_xref = CharField(max_length=16, null=True) modified_date = DateTimeField(default=datetime.datetime.now) ''' def _build_xrefs(self): from core import cms cms.purge_fileinfos(self.fileinfos) import re iterable_tags = ( (re.compile('%Y'), 'Y'), (re.compile('%m'), 'M'), (re.compile('%d'), 'D'), (re.compile('\{\{page\.categories\}\}'), 'C'), (re.compile('\{\{page\.primary_category.?[^\}]*\}\}'), 'C'), # Not yet implemented (re.compile('\{\{page\.user.?[^\}]*\}\}'), 'A') (re.compile('\{\{page\.author.?[^\}]*\}\}'), 'A') ) match_pos = [] for tag, func in iterable_tags: match = tag.search(self.path_string) if match is not None: match_pos.append((func, match.start())) sorted_match_list = sorted(match_pos, key=lambda row: row[1]) context_string = "".join(n for n, m in sorted_match_list) self.archive_xref = context_string content_type = self.template.template_type if content_type == 'Page': cms.build_pages_fileinfos(self.template.blog.pages()) if content_type == 'Archive': cms.build_archives_fileinfos(self.template.blog.pages()) if content_type == 'Index': cms.build_indexes_fileinfos(self.template.blog.index_templates()) ''' @property def fileinfos(self): ''' Returns a list of all fileinfos associated with the selected template mapping. ''' fileinfos = FileInfo.select().where(FileInfo.template_mapping == self) return fileinfos @property def next_in_mapping(self): ''' Stub for the next archive entry for a given template. Determines from xref map. We should have the template mapping as part of a context as well. I don't think we do this yet. ''' pass @property def previous_in_mapping(self): pass @property def first_in_mapping(self): pass @property def last_in_mapping(self): pass
class Template(BaseModel): title = TextField(default="Untitled Template", null=False) theme = ForeignKeyField(Theme, null=False, index=True) template_type = CharField(max_length=32, index=True, null=False) blog = ForeignKeyField(Blog, null=False, index=True) body = TextField(null=True) publishing_mode = CharField(max_length=32, index=True, null=False) external_path = TextField( null=True) # used for linking in an external file modified_date = DateTimeField(default=datetime.datetime.now) is_include = BooleanField(default=False, null=True) default_type = CharField(max_length=32, default=None, null=True) def include(self, include_name): include = Template.get(Template.title == include_name, Template.theme == self.theme.id) return include.body @property def includes(self): # get most recent fileinfo for page # use that to compute includes # we may want to make that something we can control the context for pass @property def mappings(self): ''' Returns all file mappings for the template. ''' template_mappings = TemplateMapping.select().where( TemplateMapping.template == self) return template_mappings @property def fileinfos(self): ''' Returns a list of all fileinfos associated with the selected template. ''' fileinfos = FileInfo.select().where( FileInfo.template_mapping << self.mappings) return fileinfos @property def fileinfos_published(self): if self.template_type == "Page": return self.fileinfos.select().join(Page).where( Page.status == page_status.published) else: if self.publishing_mode != publishing_mode.do_not_publish: return self.fileinfos @property def default_mapping(self): ''' Returns the default file mapping for the template. ''' default_mapping = TemplateMapping.select().where( TemplateMapping.template == self, TemplateMapping.is_default == True).get() return default_mapping