class Role(DataObject, db.Model): name = db.Column(db.String(50), unique=True) description = db.Column(db.String(255)) def __init__(self, name, description): self.name = name self.description = description
class ErrorPage(SiteTree): id = alchemy.Column(alchemy.Integer, alchemy.ForeignKey('sitetree.id'), primary_key=True) content = alchemy.Column(alchemy.UnicodeText) content_json = alchemy.Column(alchemy.UnicodeText) error_code = alchemy.Column(alchemy.Integer, nullable=False)
class UserRoles(db.Model): __tablename__ = "user_role" user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), primary_key=True) role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'), primary_key=True)
class SuperPage(SiteTree): db = {'second_content': 'UnicodeText'} __abstract_inherit__ = [Page] second_content = alchemy.Column(alchemy.UnicodeText) subtitle = alchemy.Column(alchemy.String) has_one = {'header_image': 'ImageObject'} has_many = {'images': 'GalleryImage'} many_many = {'sample_images': 'GalleryImage'} # template = "superpage.html" allowed_children = ["SuperPage"] icon = 'glyphicon glyphicon-flash' def get_cms_form(self): from wtforms import fields from silverflask.fields import AsyncFileUploadField # Setup Gridfield button_list = [] button_list.append(GridField.AddButton()) g = GridField(parent_record=self, query=self.images, controlled_class=GalleryImage, buttons=button_list, field_name="images", display_cols=[{ "name": "id", "hidden": True }, "caption", { "name": "sort_order", "hidden": True }], name="images", sortable=True) form = OrderedFormFactory() form.add_to_tab("Root.Main", fields.StringField(name="name", default=1)) form.add_to_tab("Root.Main", fields.TextAreaField(name="content"), before="asdasd") form.add_to_tab( "Root.Main", AsyncFileUploadField(ImageObject, name="header_image_id")) form.add_to_tab("Root.Gallery", g) form.add_to_tab("Root.Buttons", SubmitField("Save", name="Submit")) form.add_to_tab("Root.Buttons", SubmitField("Publish", name="Publish")) return form
class FileObject(DataObject, db.Model): """ Contains file information :ivar location: Location of the file :ivar name: Name of the file (usually filename without extension) :ivar type: Contains """ location = db.Column(db.String(250)) name = db.Column(db.String(250)) type = db.Column(db.String(50)) __mapper_args__ = { 'polymorphic_identity': 'fileobject', 'polymorphic_on': type } def __init__(self, file=None, location=None, folder=None): if file: location = current_app.storage_backend.store(file, location) print("Saving Location: ", location) self.location = location self.name = os.path.splitext(location)[0] def url(self): """ Return the url for this file (handled by the :class:`FileStorageBackend` """ return current_app.storage_backend.get_url(self.location) def delete_file(self): """ Delete this file also from disk. """ current_app.storage_backend.delete(self.location) self.location = "" self.name = "DELETED" @classmethod def get_cms_form(cls): form = super().get_cms_form() # form.location = fields.FileField("File") return form def as_dict(self): d = super().as_dict() d.update({"id": self.id, "name": self.name, "location": self.location}) return d
class SiteConfigExtension(db.Model): __tablename__ = SiteConfig.__tablename__ __table_args__ = {'extend_existing': True} background_color = db.Column(db.String(50)) def __init__(self, baseclass): """overwrite methods of parent class""" pass
class GalleryImage(OrderableMixin, DataObject, db.Model): caption = db.Column(db.String(250)) image_id = db.Column(db.ForeignKey(FileObject.id)) image = db.relationship("FileObject") page_id = db.Column(db.ForeignKey("superpage.id")) page = db.relationship("SuperPage") @classmethod def get_cms_form(cls): form = Form form.caption = fields.StringField("Caption") form.image_id = AsyncFileUploadField(relation=ImageObject) form.page_id = fields.IntegerField() form.submit = fields.SubmitField("Submit") return form def __init__(self): self.init_order()
class SiteConfig(DataObject, db.Model): """ class SiteConfig Holds global variables such as theme selection, site title or tagline. :ivar title: page title (shown in <title> tag) :ivar tagline: tagline of the website, can be used in the template :ivar theme: Not used now, could later hold the location of a template folder """ __table_args__ = {'extend_existing': True} title = db.Column(db.String(250)) tagline = db.Column(db.String(250)) theme = db.Column(db.String(250)) @staticmethod def get_available_themes(): return [ (theme.identifier, theme.name) for theme in current_app.silverflask_theme_manager.themes.values() ] def get_cms_form(cls): form = OrderedFormFactory() form.add_to_tab("Root.Main", fields.StringField(name='title')) form.add_to_tab("Root.Main", fields.StringField(name='tagline')) form.add_to_tab( "Root.Main", fields.SelectField(name='theme', choices=cls.get_available_themes())) form.add_to_tab("Root.Buttons", fields.SubmitField("Save", name='Save')) return form @classmethod def get_current(cls): return cls.query.one()
class PolymorphicMixin(object): type = db.Column(db.String(50)) @declared_attr def __mapper_args__(cls): if hasattr(cls, '__versioned_draft_class__'): # Use same identities as draft class ident = cls.__versioned_draft_class__.__mapper_args__[ "polymorphic_identity"] else: ident = cls.__tablename__ d = { 'polymorphic_identity': ident, } # this is a base object, therefore we are not # redefining the column on which it is polymorphic if hasattr(cls.__table__.columns, 'id') and not cls.__table__.columns.id.foreign_keys: d['polymorphic_on'] = 'type' return d
class SiteTree(VersionedMixin, PolymorphicMixin, OrderableMixin, DataObject, db.Model): """ The SiteTree is the database model from which all pages have to inherit. It defines the parent/children relationships of the page tree. It also defines everything that's needed to get nice URL slugs working. """ parent_id = db.Column(db.Integer, db.ForeignKey('sitetree.id')) name = db.Column(db.String) urlsegment = db.Column(db.String(250), nullable=False) template = "page.html" children = db.relationship( 'SiteTree', cascade="all", # many to one + adjacency list - remote_side # is required to reference the 'remote' # column in the join condition. backref=db.backref("parent", remote_side='SiteTree.id'), order_by='SiteTree.sort_order') allowed_children = [] can_be_root = True icon = 'glyphicon glyphicon-file' def get_siblings(self): return SiteTree.query.filter(SiteTree.parent_id == self.parent_id) @classmethod def default_template(cls): return cls.__name__.lower() + ".html" def parents(self): tree_el = self.parent parents = [] while tree_el: parents.append(tree_el) tree_el = tree_el.parent return parents @staticmethod def get_sitetree(parent_id=None): base_page = SiteTree.query.filter(SiteTree.parent_id == parent_id)\ .order_by(SiteTree.sort_order.asc()) dest_list = [] for p in base_page: dest_dict = {} SiteTree.recursive_build_tree(p, dest_dict) dest_list.append(dest_dict) return dest_list @staticmethod def recursive_build_tree(root_node, dest_dict): dest_dict.update(root_node.jqtree_dict()) children = root_node.children if children: dest_dict['children'] = [] for child in children: temp_dict = {} dest_dict['children'].append(temp_dict) SiteTree.recursive_build_tree(child, temp_dict) else: return @classmethod def get_cms_form(cls): form = super().get_cms_form() return form def append_child(self, child): self.children.append(child) def parent_at_level(self, level=1): level = level - 1 parents = self.parents() n_parents = len(parents) if n_parents > level: return parents[level] if n_parents == level: return self else: return False def menu(self, level=1): # This returns the menu with # self as reference level = level - 1 parents = self.parents() n_parents = len(parents) if n_parents > level: cls = self.__class__ level_parent = parents[level] result = cls.query.filter(cls.parent_id == level_parent.id).all() return result if n_parents == level: return self.children else: return False def as_dict(self): d = dict() try: d = super().as_dict() except Exception as e: """ This is due to problems with wsgi and reloading: http://stackoverflow.com/questions/9722343/python-super-behavior-not-dependable """ for c in self.__class__.mro(): if c != self.__class__: super_object = super(c, self) if hasattr(super_object, 'as_dict'): d.update(super_object.as_dict()) d.update({ "parent_id": self.parent_id, "name": self.name, "type": self.type }) return d def jqtree_dict(self): return { "text": self.name, "parent_id": self.parent_id, "created_on": self.created_on, "type": self.__class__.__name__, "li_attr": { "data-pageid": str(self.id) }, "a_attr": { "href": url_for("PagesCMSController.edit_page", page_id=self.id) } } @staticmethod def get_by_url(url, cls=None): if not cls: cls = SiteTree vars = url.split('/') node = cls.query.filter(cls.urlsegment == vars[0]).first() if not node: abort(404) for var in vars[1:]: node = cls.query.filter(cls.urlsegment == var, cls.parent_id == node.id).first() if not node: abort(404) return node def get_url(self): if self.urlsegment == current_app.config[ "SILVERFLASK_HOME_URLSEGMENT"]: return "/" self_url = "/" + self.urlsegment url = "" el = self while el.parent: url = "/" + el.parent.urlsegment el = el.parent url += self_url return url def set_parent(self, parent_id): if not parent_id: if self.can_be_root: self.parent_id = None return else: raise Exception("This page type can not be a root node!") else: parent = SiteTree.query.get(int(parent_id)) if parent: if hasattr( parent, 'allowed_children' ) and self.__class__.__name__ in parent.allowed_children: self.parent_id = parent_id else: raise Exception("Parent not allowed!") else: raise Exception("Parent not existing!") return def __init__(self): pass # self.database.extend(super(SiteTree, self).database) @classmethod def create_slug(cls, target, id=None): possible_slug = slugify(target.name, to_lower=True) slug = possible_slug count = 0 def get_query(target, slug, id=None): query = cls.query.filter(cls.parent_id == target.parent_id, cls.urlsegment == slug) if id: query = query.filter(cls.id != id) return query while get_query(target, slug, id).count() > 0: slug = "{0}-{1}".format(possible_slug, count) target.urlsegment = slug @classmethod def pagetypes(cls): polymorphic_map = cls.__mapper__.polymorphic_map sitetree_props = {} for mapper in polymorphic_map.values(): if mapper.class_ != cls: mapped_class = mapper.class_ sitetree_props[mapped_class.__name__] = { 'allowed_children': mapped_class.allowed_children, 'icon': mapped_class.icon if mapped_class.icon else 'default' } return sitetree_props @classmethod def before_insert(cls, mapper, context, target): target.create_slug(target) @classmethod def before_update(cls, mapper, context, target): target.create_slug(target, target.id)
class OrderableMixin(object): """ A mixin that makes a DataObject sortable by adding a sort_order database field. Classes with this mixin automatically keep the sort_order in sync. """ sort_order = db.Column(db.Integer, nullable=False, default=1) default_order = "sort_order ASC" def insert_after(self, index, orderable_base_class=None, index_absolute=True, query=None): """ Inser after index variable :param index: index (this is the sort_order variable of the element that you want to insert after!) :param orderable_base_class: baseclass (useful in certain circumstances e.g. gridfields) :return: nothing """ if orderable_base_class: cls = orderable_base_class else: cls = self.__class__ cls.check_duplicates() query = query if query else db.session.query(cls) if not index_absolute: index_el = query.order_by( cls.sort_order.asc()).limit(1).offset(index).scalar() sort_order_index = index_el.sort_order db.session.query(cls)\ .filter(cls.sort_order > sort_order_index)\ .update({cls.sort_order: cls.sort_order + 1}) self.sort_order = index + 1 if hasattr(cls, 'LiveType'): # Repeat the same for the live version of the page cls = cls.LiveType cls.check_duplicates() live_self = cls.query.get(self.id) live_self.check_duplicates() db.session.commit() query = db.session.query(cls) if not index_absolute: print(query) print(index) print( str( query.order_by( cls.sort_order.asc()).limit(1).offset(index))) print(query.order_by(cls.sort_order.asc()).all()) index_el = query.order_by( cls.sort_order.asc()).limit(1).offset(index).scalar() index = index_el.sort_order db.session.query(cls) \ .filter(cls.sort_order > index) \ .update({cls.sort_order: cls.sort_order + 1}) live_self.sort_order = index + 1 @classmethod def reindex(cls): """ Reindexes the table. The sort order field can have "jumps" in it (e.g. 1, 4, 5, 8, 9) and reindex brings that back to a linearly ascending order: (1,2,3,4...) """ for index, el in enumerate( db.session.query(cls).order_by(cls.sort_order.asc())): el.sort_order = index db.session.commit() @classmethod def check_duplicates(cls): """ Check the table for duplicates and if there are duplicates reindex :return: nothing """ duplicates = db.session.query(cls).group_by(cls.sort_order)\ .having(func.count(cls.id) > 1).count() if duplicates > 0: cls.reindex() def move_after(self, obj): """ Move current DataObject after index (= sort_order) of another element :param index: obj element where to move after or sort order of other elements :return: nothing """ if hasattr(obj, 'sort_order'): self.insert_after(obj.sort_order) else: _id = int(obj) if _id <= 0: sort_order = 0 else: sort_order = db.session.query( self.__class__).get(_id).sort_order self.insert_after(sort_order) @classmethod def before_insert(cls, mapper, connection, target): target.init_order() def init_order(self): """ Sort element to the end """ cls = self.__class__ if not self.sort_order: query = db.session.query(func.max(cls.sort_order)) v = query[0] if v[0]: self.sort_order = v[0] + 1 else: self.sort_order = 1
class DataObject(object): """ The DataObject class is the basic building block of any CMS Element. It is a mixin that provides three basic database columns: Attributes: :ivar id: Primary key, integer id (use for joins and relationships) :ivar created_on: the datetime when the DataObject was created :ivar last_modified: the datetime when the DataObject was last modified """ @declared_attr def __tablename__(cls): """tablename, defaults to the classname in lowercase""" return cls.__name__.lower() id = db.Column(db.Integer(), primary_key=True) created_on = db.Column(db.DateTime, default=db.func.now()) last_modified = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now()) singular_name = None plural_name = None # has_one = {} # has_many = {} # many_many = {} # belongs_many_many = {} default_order = None auto_form_exclude = ['id', 'created_on', 'last_modified'] # summary_fields = [] # searchable_fields = [] # allowed_actions = [] # class CMSForm(Form): # name = fields.StringField("asdsadsa") # submit = fields.SubmitField("Submit") def __new__(cls, *args, **kwargs): def before_insert_listener(mapper, connection, target): for c in target.__class__.mro(): if hasattr(c, "before_insert"): c.before_insert(mapper, connection, target) def before_update_listener(mapper, connection, target): for c in target.__class__.mro(): if hasattr(c, "before_update"): c.before_update(mapper, connection, target) event.listen(cls, 'before_insert', before_insert_listener) event.listen(cls, 'before_update', before_update_listener) event.listen(cls, 'before_insert', lambda m, c, t: t.can_create(m, c)) event.listen(cls, 'before_update', lambda m, c, t: t.can_edit(m, c)) event.listen(cls, 'before_delete', lambda m, c, t: t.can_delete(m, c)) if not cls.singular_name: cls.singular_name = uncamel(cls.__name__) cls.plural_name = uncamel(cls.__name__ + "s") return super().__new__(cls) @classmethod def query_factory(cls): return db.session.query(cls).order_by(cls.last_modified.desc()) @classmethod def get_cms_form(cls): """ Build and return Form class. If you want to define your custom CMS Object, you likely want to override the default CMS Form. E.g.:: from wtforms import fields def get_cms_form(cls): form = super().get_cms_form() form.textfield = fields.StringField("Textfield") return form :returns: Form Class (has to be instantiated!). """ if hasattr(cls, "CMSForm"): return cls.CMSForm form_factory = OrderedFormFactory() form_fields = model_fields(cls, db_session=db.session, exclude=cls.auto_form_exclude) for key in sorted(form_fields.keys()): form_fields[key].kwargs['name'] = key form_factory.add_to_tab("Root.Main", form_fields[key]) form_factory.add_to_tab("Root.Buttons", fields.SubmitField("Save", name="Save")) return form_factory def as_dict(self): """ Get object as dict. Very useful for json responses. :return: dict with all database columns """ return {c.name: getattr(self, c.name) for c in self.__table__.columns} def can_create(self, mapper, connection): if request and not current_user: raise CannotCreateError return True def can_edit(self, mapper, connection): if request and not current_user: raise CannotUpdateError return True def can_delete(self, mapper, connection): if request and not current_user: raise CannotDeleteError return True
def create_live_table(cls): tablename = cls.__tablename__ + "_live" if tablename in created_tables or cls.__tablename__ in created_tables: return created_tables.append(tablename) columns = [] logger.debug("Creating Live table for: %s" % cls.__tablename__) ins = sainspect(cls) versioned_basetable = tablename baseclass = tuple() for c in inspect.getmro(cls): if c != cls and c.__name__ in versioned_classes: versioned_basetable = c.__table__.name + "_live" baseclass = (c.LiveType, ) + baseclass elif c != cls and c.__name__ != "VersionedMixin": baseclass = (c, ) + baseclass # Reverse baseclass mro baseclass = baseclass[::-1] for c in cls.__table__.columns: # What is happening # TODO: Check if this also works with different relationships... new_keys = [] if c.foreign_keys: for k in c.foreign_keys: key_target = k.column.table.name if key_target in versioned_tables: new_keys.append( db.ForeignKey(key_target + "_live." + k.column.key)) else: new_keys.append(db.ForeignKey(k.target_fullname)) columns.append( db.Column(c.key, db.Integer(), new_keys[0], primary_key=c.primary_key, default=c.default)) else: columns.append(c.copy()) cls.LiveTable = table = sa.schema.Table(tablename, cls.__table__.metadata, *columns) args = {} for key, value in cls.__dict__.items(): if type(value) is types.FunctionType: args.update({key: value}) args.update({"__table__": table}) for column in columns: args[column.name] = column backrefs = [] rs = [r for r in ins.relationships] for r in rs: if r.parent == cls.__mapper__: print("There is a relation defined %s with Key: %r" % (cls.__name__, r)) if hasattr(r.target, 'fullname') and r.target.fullname.endswith("version"): continue if r.key in backrefs: continue key = r.key target = "" if hasattr(r.target, 'fullname') and r.target.fullname in versioned_tables: target = r.mapper.entity.__name__ + "Live" else: if r.direction == MANYTOONE: args[key] = db.relationship(r.mapper) elif r.direction == MANYTOMANY: # if hasattr(r, 'secondary') and r.secondary: # print("SECONDARY for ... ", r) # kwargs['secondary'] = r.secondary primaryjoin = copy.copy(r.primaryjoin) primaryjoin.left = args[primaryjoin.left.key] secondaryjoin = copy.copy(r.secondaryjoin) args[key] = db.relationship( r.mapper, viewonly=True, primaryjoin=primaryjoin, foreign_keys=[primaryjoin.right, secondaryjoin.right], secondary=r.secondary, secondaryjoin=secondaryjoin) else: primaryjoin = copy.copy(r.primaryjoin) primaryjoin.left = args[primaryjoin.left.key] args[key] = db.relationship( r.mapper, viewonly=True, primaryjoin=primaryjoin, foreign_keys=[primaryjoin.right]) continue kwargs = {} if hasattr(r, "backref") and r.backref: backref_key = r.backref[0] backrefs.append(backref_key) backref = r.backref[1] remote_side = None if hasattr(backref["remote_side"].cls, "LiveType") or backref["remote_side"].cls == cls: orig_arg = backref["remote_side"].arg arg_v = orig_arg.split(".") arg_v[0] += "Live" remote_side = ".".join(arg_v) kwargs['backref'] = db.backref(backref_key, remote_side=remote_side) args[key] = db.relationship(target, cascade="none, ", **kwargs) if args.get("before_insert"): del args["before_insert"] if args.get("before_update"): del args["before_update"] if args.get("__versioned__"): del args["__versioned__"] args["template"] = getattr(cls, "template") if hasattr(cls, "template") else "" mapper_args = {} if hasattr(cls, "__mapper_args__"): mapper_args = cls.__mapper_args__ args['__versioned_draft_class__'] = cls args['__create_live__'] = False cls.LiveType = type('%sLive' % cls.__name__, baseclass, args) cls.LiveType.__create_live__ = False for key, arg in mapper_args.items(): if key == "polymorphic_on": mapper_args[key] = table.columns[arg] live_mapper = mapper(cls, cls.LiveTable, non_primary=True, **mapper_args) cls.live_mapper = live_mapper return cls.LiveTable
class ImageObject(FileObject): """ A basic ImageObject, that inherits all properties from the file object and adds some functionality related to images, such as cropping and resizing, as well as caching the cropped and/or resized images. """ __tablename__ = "imageobject" __mapper_args__ = {'polymorphic_identity': __tablename__} # Specifies special operations on Image Files id = db.Column(db.Integer, db.ForeignKey('fileobject.id'), primary_key=True) __cache_dir__ = "_resized" def resize(self, width=None, height=None, mode='crop', background="white"): """ Resize image :param width: define width in pixels :param height: height in pixels. If no height is set, it will be set to width (i.e. the result will be a square) :param mode: string, one of 'crop' (default), 'pad', 'fit' or 'reshape' :param background: background color, as string (i.e. 'blue' or hex '#F0F000'). As supported by the ImageColor module of Pillow. """ if not height: height = width def resized_location(): orig_url = self.location (orig_folder, tail) = os.path.split(orig_url) (fn, ext) = os.path.splitext(tail) cache_path = os.path.join(orig_folder, self.__cache_dir__) new_fn = fn + mode + str(width) + str(height) + ext full_path = os.path.join(cache_path, new_fn) return cache_path, new_fn, full_path resized_path, resized_filename, resize_fullpath = resized_location() if current_app.storage_backend.exists(resize_fullpath): return current_app.storage_backend.get_url(resize_fullpath) else: fp = current_app.storage_backend.retrieve(self.location) unused_var, ext = os.path.splitext(self.location) resized_img = self._resize(fp, width, height, mode, background) if not resized_img: return "" tmp_file_path = "/tmp/" + str(uuid.uuid4()) + ext resized_img.save(tmp_file_path) tmp_file = open(tmp_file_path, "rb") current_app.storage_backend.store(tmp_file, resized_path, filename=resized_filename) return current_app.storage_backend.get_url(resize_fullpath) @staticmethod def _resize(filepointer, width=None, height=None, mode=None, background=None): print("resizing %r %r %r %r" % (filepointer, width, height, mode)) try: img = Image.open(filepointer) except: return False orig_width, orig_height = img.size width = min(width, orig_width) if width else None height = min(height, orig_height) if height else None if not img.mode.lower().startswith('rgb'): img = img.convert('RGBA') if width and height: fit, crop = sorted([(width, orig_height * width // orig_width), (orig_width * height // orig_height, height)]) if mode == 'fit' or mode == 'pad': img = img.resize(fit, Image.ANTIALIAS) if mode == 'pad': pad_color = str(background or 'black') back = Image.new('RGBA', (width, height), pad_color) back.paste(img, ((width - fit[0]) // 2, (height - fit[1]) // 2)) img = back elif mode == 'crop': dx = (crop[0] - width) // 2 dy = (crop[1] - height) // 2 img = img.resize(crop, Image.ANTIALIAS).crop( (dx, dy, dx + width, dy + height)) elif mode == 'reshape' or mode is None: img = img.resize((width, height), Image.ANTIALIAS) else: raise ValueError('unsupported mode %r' % mode) elif width: height = orig_height * width // orig_width img = img.resize((width, height), Image.ANTIALIAS) elif height: width = orig_width * height // orig_height img = img.resize((width, height), Image.ANTIALIAS) return img
class User(DataObject, UserMixin, db.Model): """ The base User model. Defines the following fields: username = String, and unique, firstname, lastname = String email = String, unique The password is set with user.set_password("password"), and then stored with encryption. """ username = db.Column(db.String, unique=True) firstname = db.Column(db.String) lastname = db.Column(db.String) email = db.Column(db.String, unique=True) password = db.Column(db.String) confirmed_at = db.Column(db.DateTime) is_enabled = db.Column(db.Boolean(), nullable=False, server_default='1') roles = db.relationship('Role', secondary=UserRoles.__tablename__, backref=db.backref('users', lazy='dynamic')) auto_form_exclude = DataObject.auto_form_exclude + [ 'confirmed_at', 'is_enabled' ] def __init__(self, username, password, email=None, is_enabled=True): self.username = username self.password = current_app.user_manager.hash_password(password) self.email = email self.is_enabled = is_enabled @property def name(self): return str(self.firstname) + " " + str(self.lastname) def is_authenticated(self): if isinstance(self, AnonymousUserMixin): return False else: return True def is_active(self): return True def is_anonymous(self): if isinstance(self, AnonymousUserMixin): return True else: return False def get_id(self): return self.id def __repr__(self): return '<User %r>' % self.username def set_password(self, new_password): self.password = current_app.user_manager.hash_password(new_password) def as_dict(self): d = super().as_dict() if d["password"]: del d["password"] d.update({"name": self.name}) return d @classmethod def get_cms_form(cls): form = super().get_cms_form() roles = [(r.id, r.name) for r in db.session.query(Role).all()] form.add_to_tab( "Root.Main", fields.PasswordField("New Password", [ validators.EqualTo('new_password_confirmation', message='Passwords must match') ], name='new_password')) form.add_to_tab( "Root.Main", fields.PasswordField("Repeat New Password", name='new_password_confirmation')) form.add_to_tab( "Root.Main", QuerySelectMultipleField('Role', query_factory=lambda: Role.query, get_label=lambda x: x.name, get_pk=lambda x: x.id, name='roles')) del form.fields["password"] return form