class AccountInfo(MonDoc): _id = StrField(desc="User id", title="User Id", required=True, readOnly=True) bio = TextAreaField(desc="Biography of user (Markdown)", cols=60, rows=8, monospaced=True) bioHtml = TextAreaField(desc="bio compiled to HTML", monospaced=True, readOnly=True, displayInForm=False) title = StrField(title="Title of Blog") following_ids = FKeys('AccountInfo', title="Following", readOnly=True, desc="users this user is following") realName = StrField( desc="your real name or anything else you want to put here") def url(self): """ The URL for an acocunt is that accounts blog page """ return "/blog/" + self._id @classmethod def classLogo(self): return "<i class='fa fa-user-o'></i> " def preCreate(self): self.title = form("{id}'s blog", self.asReadableH('_id')) def preSave(self): """ before saving, create the bioHtml """ self.bioHtml, _ = mark.render(self.bio)
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 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 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 Book(MonDoc): title = StrField() yearPublished = IntField() authors_ids = FKeys(Author, title="Authors") @classmethod def classLogo(cls): return "<i class='fa fa-book'></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()
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 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 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 TheTestForm(FormDoc): aaa = StrField() aNumber = IntField(minValue=0, maxValue=100) cost = FloatField(title="Cost, £", formatStr="{:.2f}") tickyBox = BoolField() toggleSwitch1 = BoolField(widget='toggleSwitch') toggleSwitch2 = BoolField(widget='toggleSwitch') showBody = BoolField(widget='toggleSwitch') order = BoolField(widget='toggleSwitch', showTitle=False, onText="Oldest First", offText="Newest First") favouriteFruit = ChoiceField(choices=FRUIT_CHOICES, showNull=True, allowNull=False) slots = MultiChoiceField(choices=SLOT_CHOICES, required=True) note = TextAreaField() dateOfBirth = DateField(required=True)
class Author(MonDoc): name = StrField() notes = TextAreaField() dateOfBirth = DateField() @classmethod def classLogo(cls): return "<i class='fa fa-pencil'></i> " def myBooks(self) -> Iterable['Book']: """ return this author's books """ return self.getForeignDocs('Book') def myBooksLinks(self) -> str: """ return html giving a list of this author's books, with each book being a hyperlink to it. I.e. return HTML containing a series of <a href> elements, each containing the name of a book and a link to it. """ return ", ".join(bk.a() for bk in self.myBooks())
class CreateAccountForm(FormDoc): userName = StrField(title="Enter New Username", required=True, autocomplete=False, minLength=4, maxLength=30, charsAllowed=USER_ID_CHARS) password = PasswordField(title="Enter New Password", required=True, autocomplete=False, minLength=4, maxLength=30, charsAllowed=USER_ID_CHARS) confirmPassword = PasswordField(title="Confirm Password", required=True, autocomplete=False, minLength=4, maxLength=30, charsAllowed=USER_ID_CHARS) def formWideErrorMessage(self) -> str: if self.password != self.confirmPassword: return ("The New Password and Confirm Password fields " "must be the same.") return ""
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
class LoginForm(FormDoc): userName = StrField() password = PasswordField()
class User(MonDoc): userName = StrField(charsAllowed=string.ascii_letters + string.digits + "_", minLength=2, desc="user name") hashedPassword = StrField() pw = StrField(desc="unencrypted password, for testing") password = StrField(default=HIDDEN) # for altering on /user page email = StrField(monospaced=True) isAdmin = BoolField(desc="is this user an admin?", title="Is Admin?", default=False) isActive = BoolField(desc="is this an active user?", title="Is Active?", default=True) @classmethod def classLogo(cls): return "<i class='fa fa-user'></i> " def __repr__(self): """ Return a string representation of myself. @return::str """ s = "<User %r pw=%r email=%r>" % (self.userName, self.pw, self.email) return s #========== stuff Flask-login needs: ========== """ see <https://flask-login.readthedocs.org/en/latest/#your-user-class> """ def get_id(self): return self.userName @property def is_authenticated(self): return self.isAuthenticated() @property def is_anonymous(self): return not self.has_key('_id') @property def is_active(self): return True def isAuthenticated(self): """ Do we have a logged-in user? @return::bool """ return self.has_key('_id') #========== def preSave(self): """ We don't want to save the plaintext password to the database. """ if self.password != HIDDEN: self.pw = self.password self.hashedPassword = hashPassword(self.password) self.password = HIDDEN # userName is a unique identifier, so use this as the _id self._id = self.userName #========== for display ========== def blogLink(self) -> str: """ return a link to the user's blog """ h = form("<a href='/blog/{userName}'>@{userName}</a>", userName=htmlEsc(self.userName)) return h