class GenericMedia(Titled, TimeStampped, UserRelated, Tagged): file_id = db.Column( db.String(64), unique=True, nullable=False, index=True, default=lambda: token_urlsafe(NUMBYTES), ) description = db.Column(db.Text, nullable=True, info=dict(lable=lazy_gettext("Description"))) __depot_args__ = {"upload_storage": "media_storage"} @declared_attr def uploaded_file(cls): depot_args = dict(cls.__depot_args__) filters = depot_args.pop("filters", []) allowed_files = getattr(cls, "__allowed_file_types__", []) if allowed_files: filters.insert(0, FileTypeCheckFilter(filetypes=allowed_files)) return db.Column( UploadedFileField(filters=filters, **depot_args), nullable=False, info=dict(label=lazy_gettext("Select a file")), ) def before_insert(self, mapper, connection): tbl = mapper.mapped_table sel = db.select([tbl.c.id]) token = token_urlsafe(14) while connection.scalar( db.func.count(sel.where(tbl.c.file_id == token))): token = token_urlsafe(NUMBYTES) filename, ext = os.path.splitext(self.uploaded_file.filename) self.file_id = f"{slugify(filename)}-{token}{ext}" @classmethod def get_upload_storage(cls): return (db.inspect(cls).get_property( "uploaded_file").columns[0].type._upload_storage) @staticmethod def deserialize_instance(module, model, **attrs): """Custom logic for constructing object from json when adding demo data.""" with db.session.no_autoflush: file = open( os.path.join(module.root_path, "fixtures", attrs["uploaded_file"]), "rb") attrs["uploaded_file"] = file obj = original_deserialize_instance(module, model, **attrs) file.close() return obj
class RichTextPage(Page): __contenttype__ = "richtext_page" __metadescription_column__ = "content" id = db.Column(db.Integer, db.ForeignKey("page.id"), primary_key=True) content = db.deferred( db.Column( db.UnicodeText, nullable=False, info=dict( label="Content", description="Page content", mimetype="text/html" ), ) )
def file_value(cls): kwargs = getattr(cls, "__file_field_args__", {}) nullable = kwargs.pop("nullable", True) info = kwargs.pop("info", {}) info["type"] = ((FileIntent, FileStorage), "file") return db.Column(UploadedFileField(**kwargs), nullable=nullable, info=info)
class Form(Page): __contenttype__ = "form" id = db.Column(db.Integer, db.ForeignKey("page.id"), primary_key=True) submit_text = db.Column( db.String(255), info=dict( label=lazy_gettext("Submit button text"), description=lazy_gettext("Text of the submit button in the form"), ), ) submit_message = db.Column( db.UnicodeText, info=dict( label=lazy_gettext("After Submit Message"), description=lazy_gettext( "A Message to display for the user after submitting the form"), ), ) fields = db.relationship(Field, backref="form")
def uploaded_file(cls): depot_args = dict(cls.__depot_args__) filters = depot_args.pop("filters", []) allowed_files = getattr(cls, "__allowed_file_types__", []) if allowed_files: filters.insert(0, FileTypeCheckFilter(filetypes=allowed_files)) return db.Column( UploadedFileField(filters=filters, **depot_args), nullable=False, info=dict(label=lazy_gettext("Select a file")), )
class Document(db.Model, GenericMedia): id = db.Column(db.Integer, primary_key=True) file = synonym("uploaded_file") __allowed_file_types__ = ("document",) __depot_args__ = {"upload_storage": "document_storage"} @property def url(self): if "media" in current_app.blueprints: return url_for("media.documents", file_id=self.file_id)
class Image(db.Model, GenericMedia): id = db.Column(db.Integer, primary_key=True) # Nicer aliases for template designers file = synonym("uploaded_file") alt = synonym("title") caption = synonym("description") # Uploaded file arguments __allowed_file_types__ = ("raster-image",) IMG_SIZES = dict(xs=(64, 64), sm=(128, 128), md=(320, 320), lg=(512, 512)) __depot_args__ = { "upload_storage": "image_storage", "upload_type": uploaded_image(thumbnail_sizes=IMG_SIZES), } @property def url(self): if "media" in current_app.blueprints: return url_for("media.images", file_id=self.file_id)
class Redirect(db.Model, SQLAEvent): id = db.Column(db.Integer, primary_key=True) from_url = db.Column( db.String(1024), nullable=False, unique=True, index=True, info=dict( label=lazy_gettext("Redirect From"), description=lazy_gettext("Old URL to redirect from"), ), ) to_url = db.Column( db.String(1024), info=dict( label=lazy_gettext("Redirect to"), description=lazy_gettext("Redirect to this URL"), ), ) to_page_id = db.Column(db.Integer, db.ForeignKey(Page.id)) permanent = db.Column( db.Boolean, default=False, info=dict( label=lazy_gettext("Permanent Redirect"), description=lazy_gettext("Check this to make this a permanent redirect"), ), ) to_page = db.relationship( Page, backref="redirects", cascade="all", info=dict( label=lazy_gettext("Select a *Page* to Redirect to"), description=lazy_gettext(""), ), ) @validates("to_url") def norm_tourl(self, key, to_url): if to_url is not None: return self.normalize_url(to_url) @validates("from_url") def norm_fromurl(self, key, from_url): return self.normalize_url(from_url) @property def url(self): return self.to_url or self.to_page.url @staticmethod def normalize_url(url): """Taken from wagtail CMS.""" url = url.strip() url_parsed = urlparse(url) path = url_parsed[2] if not path.startswith("/"): path = "/" + path if path.endswith("/") and len(path) > 1: path = path[:-1] # Parameters must be sorted alphabetically parameters = url_parsed[3] parameters_components = parameters.split(";") parameters = ";".join(sorted(parameters_components)) # Query string components must be sorted alphabetically query_string = url_parsed[4] query_string_components = query_string.split("&") query_string = "&".join(sorted(query_string_components)) if parameters: path = path + ";" + parameters # Add query string to path if query_string: path = path + "?" + query_string return path def __repr__(self): return f"<Redirect from: {self.from_url}, to={self.url}, permanent: {self.permanent}>" def before_flush(self, session, is_modified): if self.to_url and any((self.to_page_id, self.to_page)): raise ValueError("Cannot set both URL and Page for the same redirect") elif not any((self.to_url, self.to_page, self.to_page_id)): raise ValueError("You must provide either a URL or a page to redirect to.")
class Field(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column( db.String(255), nullable=False, info=dict( label=lazy_gettext("Field Name"), description=lazy_gettext( "This is the same as the column name in the resulting data. It should contain only letters, numbers, and under scors, and should not start with a number" ), ), ) label = db.Column( db.Unicode(255), nullable=False, info=dict( label=lazy_gettext("Field Label"), description=lazy_gettext("This will be shown to the users"), ), ) description = db.Column( db.Unicode(255), default="", info=dict( label=lazy_gettext("Field Description"), description=lazy_gettext("A summary about the field"), ), ) type = db.Column( db.String(50), nullable=False, info=dict( label=lazy_gettext("Field Type"), description=lazy_gettext("HTML Type of the field"), ), ) choices = db.Column( db.Unicode, info=dict( label=lazy_gettext("Field Choices"), description=lazy_gettext("If it is a select"), ), ) default = db.Column( db.Unicode, info=dict( label=lazy_gettext("Default Value"), description=lazy_gettext("Default value to populate the field"), ), ) required = db.Column( db.Boolean, default=False, info=dict( label=lazy_gettext("Required"), description=lazy_gettext("Whether this field is required or not"), ), ) max_length = db.Column( db.Integer, default=255, info=dict( label=lazy_gettext("Maximom Length"), description=lazy_gettext("Max number of chars the user can enter"), ), ) form_id = db.Column(db.Integer, db.ForeignKey("form.id")) @validates("type") def validate_field_type(self, key, ftype): # Bypass validation to be able to install fixtures if not current_app._got_first_request: return ftype if ftype not in current_app.data["available_field_types"]: raise ValueError("Field type is not supported.") return ftype
class FieldEntry(db.Model, DynamicProp): id = db.Column(db.Integer, primary_key=True) entry_id = db.Column(db.Integer, db.ForeignKey(FormEntry.id)) entry = db.relationship("FormEntry", backref="fields") field_id = db.Column(db.Integer, db.ForeignKey("field.id")) field = db.relationship("Field", backref="entries")
class FormEntry(TimeStampped, db.Model): id = db.Column(db.Integer, primary_key=True) form_id = db.Column(db.Integer, db.ForeignKey("form.id")) form = db.relationship("Form", backref="entries")