class Foo(MonDoc): name = StrField() description = TextAreaField(monospaced=True) aNumber = IntField(minValue=0, maxValue=100) minSpeed = FloatField(title="Minimum Speed, mph", minValue=0.0) maxSpeed = FloatField(title="Maximim Speed, mph", minValue=0.0) favouriteDrink = ChoiceField(choices=DRINK_CHOICES, showNull=True, allowNull=True) fruitsLiked = MultiChoiceField(choices=FRUIT_CHOICES, desc="tick all fruits this person likes") tickyBox = BoolField() aDate = DateField() lastSaved = DateTimeField(desc="when this foo was last saved", readOnly=True) aDateTime = DateTimeField(title="A Date and Time") anything = ObjectField(desc="can contain anything", default=["any", "thing"]) favouriteBook = FK(models.Book, allowNull=True, showNull=True) @classmethod def classLogo(self): return "<i class='fa fa-star-o'></i> " def formWideErrorMessage(self): if self.minSpeed > self.maxSpeed: return "Minimum speed cannot be greater than maximum speed" return "" # no error message, validates OK def preSave(self): self.lastSaved = BzDateTime.now() d = self.mongoDict() d.pop('anything', "") # remove the anything field dpr("d=%r", d) self.anything = d
class Tag(MonDoc): _id = StrField(desc="tag id") created = DateTimeField(desc="when tag was created") lastUsed = DateTimeField(desc="when tag was most recently used") timesUsed = IntField(desc="number of times used") def getName(self) -> str: return "#" + self._id @classmethod def classLogo(cls) -> str: return "<i class='fa fa-hashtag'></i> "
class Alert(MonDoc): user_id = FK(userdb.User, desc="who this alert is for") alertType = ChoiceField(choices=ALERT_TYPES, desc="type of this alert", allowNull=False, readOnly=True) message_id = FK(Message, desc="the message this alert relates to") live = BoolField(default=True, desc="an alert is live until the user clicks to view it") created = DateTimeField(desc="when this alert was created", readOnly=True) doer_id = FK(userdb.User, desc="user who starred/replied") reply_id = FK(Message, desc="the reply", allowNull=True) def logo(self): if self.live: return "<i class='fa fa-bell'></i> " else: return "<i class='fa fa-bell-o'></i> " @classmethod def classLogo(self): return "<i class='fa fa-bell-o'></i> " def preCreate(self): """ before saving, create the bioHtml """ self.created = BzDateTime.now()
class WikiPage(MonDoc): owner_id = FK('User', allowNull=False, desc="the owner of this wikipage") pageName = StrField(desc="page name") version = IntField(desc="version number") source = TextAreaField(monospaced=True, required=True) html = TextAreaField(monospaced=True, readOnly=True) published = DateTimeField(readOnly=True, dateTimeFormat=models.MESS_TIME_DISPLAY_FORMAT) @classmethod def classLogo(cls) -> str: return "<i class='fa fa-file-text-o'></i> " def logo(self) -> str: return ("<i class='fa fa-home'></i> " if self.pageName == "home" else "<i class='fa fa-file-text-o'></i> ") def url(self) -> str: theUrl = form("/wiki/{u}/{pn}", u=self.owner_id, pn=self.pageName) return theUrl def getName(self) -> str: return self.pageName def preCreate(self): self.published = BzDateTime.now() def preSave(self): """ before saving, render the source into html """ self.html, _ = mark.render(self.source, wikiExt=True)
class UserDemographics(MonDoc): """ demographics of a user """ _id = StrField(desc="User id", title="User Id", displayInForm=False, required=True, readOnly=True) decadeBorn = ChoiceField( desc="which decade were you born in", choices=DECADE_BORN_CHOICES, showNull=True, allowNull=False) religionBroughtUp = ChoiceField( desc="religion you were brought up in", choices=RELIGION_CHOICES, showNull=True, allowNull=False) religionNow = ChoiceField( desc="religion you are now", choices=RELIGION_CHOICES, showNull=True, allowNull=False) savedAt = DateTimeField(desc="when the data was saved", displayInForm=False, readOnly=True) @classmethod def classLogo(cls): return "<i class='fa fa-male'></i> "
class WikiPage(MonDoc): _id = StrField(readOnly=True) owner_id = FK('User', allowNull=False, desc="the owner of this wikipage") folder = StrField(desc="path to folder") filename = StrField(desc="canonical filename (in folder)") version = IntField(desc="version number") source = TextAreaField(monospaced=True, required=True) html = TextAreaField(monospaced=True, readOnly=True) published = DateTimeField(readOnly=True, dateTimeFormat=models.MESS_TIME_DISPLAY_FORMAT) @classmethod def classLogo(cls) -> str: return "<i class='fa fa-file-text-o'></i> " def preCreate(self): self.published = BzDateTime.now() def canAlter(self, userName: str) -> bool: """ Can a user alter this wiki page? At the moment only the owner of a apge can alter it. Later we will enable collaborative wikis. """ return canAlter(userName, self.owner_id, self.folder, self.filename)
class FileExample(MonDoc): name = StrField() description = TextAreaField() timestamp = DateTimeField(desc="when this filex was last altered", readOnly=True) @classmethod def classLogo(self): return "<i class='fa fa-file'></i> " def preSave(self): self.timestamp = BzDateTime.now()
class WikiPage(MonDoc): _id = StrField(readOnly=True) owner_id = FK('User', allowNull=False, desc="the owner of this wikipage") folder = StrField(desc="path to folder") filename = StrField(desc="canonical filename (in folder)") version = IntField(desc="version number") source = TextAreaField(monospaced=True, required=True) html = TextAreaField(monospaced=True, readOnly=True) published = DateTimeField(readOnly=True, dateTimeFormat=models.MESS_TIME_DISPLAY_FORMAT) @classmethod def classLogo(cls) -> str: return "<i class='fa fa-file-text-o'></i> " def preCreate(self): self.published = BzDateTime.now()
class Answer(MonDoc): """ an answer by a user to a question An answer can either be a string or an integer. If it is an integer, (ans) must be "" and ani is the answer If it is a string, ans cannot be "" and ani must be 0. """ user_id = FK(userdb.User) question_id = StrField(desc="ID of question") savedAt = DateTimeField(desc="when the question was answered", readOnly=True) ans = StrField(desc="the user's answer to the question, as a string") ani = StrField(desc="the user's answer to the question, as an integer") @classmethod def classLogo(cls): return "<i class='fa fa-check-square-o'></i> " def preSave(self): self.savedAt = BzDateTime.now()
class Message(MonDoc): title = StrField(readOnly=True) source = TextAreaField(monospaced=True, required=True) html = TextAreaField(monospaced=True, readOnly=True) replyTo_id = FK('Message', allowNull=True, desc="the message this is a reply to") author_id = FK('User', allowNull=False, desc="the author of this message") tags = ObjectField() published = DateTimeField(readOnly=True, dateTimeFormat=MESS_TIME_DISPLAY_FORMAT) starredBy_ids = FKeys('User') numStars = IntField(desc='number of stars on this message') @classmethod def classLogo(cls) -> str: return "<i class='fa fa-comment-o'></i> " def preCreate(self): self.published = BzDateTime.now() def preSave(self): """ before saving, create the title """ lines = self.source.split("\n") if len(lines) >= 1: self.title = maxChars(lines[0], 80, 90) else: self.title = "" self.numStars = len(self.starredBy_ids) def url(self): u = "/mess/" + self.id() return u def fullUrl(self): return config.SITE_STUB + self.url() #========== display a message ========== def viewH(self) -> str: """ return HTML displaying this message """ h = form( """ <div class='mess'> <div class='mess-header'> {messLink}/{userLink} at {published}{replyToText} </div> {body} <p class='mess-footer'> {context} - <a href='/thread/{id}'>thread</a> - <a href='/messSource/{id}'>source</a> {reply} - {star} </p> </div>""", id=self.id(), messLink=self.linkA(), userLink=self.author.blogLink(), replyToText=self.replyToText(), published=self.asReadableH('published'), body=self.html, context=self.contextA(), reply=self.replyA(), star=self.starH(), ) return h def starH(self) -> str: """ The HTML for a star underneath a message """ h = "" c = "" if self.numStars >= 1: h = form("{} ", self.numStars) cun = currentUserName() if not cun or cun == self.author_id: #>>>>> not logged in, or author h += "<i class='fa fa-star-o fa-lg'></i> " else: #>>>>> user is not message author # has this user starred the message? starred = cun in self.starredBy_ids if starred: h += "<i class='starred fa fa-star fa-lg'></i> " c = "starred" else: h += form( """<i onclick='starClicked("{mid}")' """ "class='can_star fa fa-star-o fa-lg'></i> ", mid=self._id) c = "can_star" #//if h2 = form("<span class='{c}'>{h}</span>", c=c, h=h) return h2 def linkA(self) -> str: """ link to this message's /mess page """ h = form("<a href='/mess/{id}'>{id}</a>", id=htmlEsc(self._id)) return h def replyA(self) -> str: """ html containing the link to reply to this message. If not logged in this is empty. """ cun = currentUserName() if not cun: return "" h = form("- <a href='/messRep/{id}'>reply</a> ", id=self.id()) return h def replyToText(self) -> str: """ if this message is a reply, text in the header linking to the message it's a reply to. """ if not self.replyTo_id: return "" parent = self.replyTo if not parent: return "" h = form(" reply-to: {messLink}/{userLink}", messLink=parent.linkA(), userLink=parent.author.blogLink()) return h def contextA(self) -> str: """ if post is a reply, return html for link to context of post """ if self.isHeadPost(): return "" h = form("- <a href='/context/{id}'>context</a>", id=self.id()) return h def viewOneLine(self, showAuthor: bool = True) -> str: """ View this message as one line @parasm showAuthor = if true, show the author of this message """ publishedShort = self.asReadableH('published')[2:] title = self.asReadableH('title') authorA = "" if showAuthor: authorA = form("<a class='author' " "href='/blog/{u}'>@{u}</a> ", u=self.author_id) h = form( "<br>{publishedShort} " "{authorA}" "<a href='/mess/{id}'>{title}</a>\n", publishedShort=publishedShort, authorA=authorA, id=self.id(), title=title) return h #========== misc utility functions ========== def context(self) -> List['Message']: """ the list of messages leading up to this one, including this one, in chronological order """ if self.isHeadPost(): return [self] parent = self.getParent() if parent: return parent.context() + [self] else: return [self] def isReply(self) -> bool: return bool(self.replyTo_id) def isHeadPost(self) -> bool: return not self.isReply() def getParent(self) -> Union['Message', None]: """ if a post has a parent, return it, else return None """ if self.isHeadPost(): return None return Message.getDoc(self.replyTo_id) def getNumChildren(self) -> int: """ return the number of replies this message has """ return Message.count({'replyTo_id': self._id}) def getChildren(self): """ return an iterator to the message's replies (oldest first) returns an Iterable[Message] """ ms = Message.find({'replyTo_id': self._id}, sort='published') return ms