class UserCollection(UserRelation): instantiable = True groups_order = UserMember.groups_order member_class = schema.Collection edit_controls = [ "cocktail.html.TextArea", "cocktail.html.CheckList", "cocktail.html.MultipleChoiceSelector" ] search_controls = [ "cocktail.html.DropdownSelector", "cocktail.html.RadioSelector", "woost.views.ItemSelector", "cocktail.html.CheckList", "cocktail.html.MultipleChoiceSelector" ] member_items = schema.Reference(type=UserMember, required=True, bidirectional=True, related_key="parent_collection", integral=True, member_group="constraints") member_min = schema.Integer(listed_by_default=False, member_group="constraints") member_max = schema.Integer(min=member_min, listed_by_default=False, member_group="constraints")
class IFrameBlock(Block): instantiable = True type_group = "blocks.custom" view_class = "cocktail.html.Element" members_order = ["src", "width", "height"] src = schema.URL(required=True, member_group="content") width = schema.Integer(min=0, member_group="content") height = schema.Integer(min=0, member_group="content") def init_view(self, view): Block.init_view(self, view) view.tag = "iframe" view["src"] = self.src view["frameborder"] = "0" if self.width: view["width"] = self.width if self.height: view["height"] = self.height
class ImageFileRenderer(Renderer): """A content renderer that handles image files.""" instantiable = True max_size = schema.Tuple(items=(schema.Integer(required=True, min=1), schema.Integer(required=True, min=1)), request_value_separator="x") def can_render(self, item, **parameters): return (isinstance(item, File) and item.resource_type == "image" and item.mime_type in formats_by_mime_type and (self.max_size is None or self.validate_size(item))) def validate_size(self, file): try: width, height = get_image_size(file.file_path) except IOError: return False else: max_size = self.max_size return (width <= max_size[0] and height <= max_size[1]) \ or (width <= max_size[1] and height <= max_size[0]) def render(self, item, **parameters): return item.file_path
class UserString(UserMember): instantiable = True groups_order = UserMember.groups_order member_class = schema.String edit_controls = [ "cocktail.html.TextBox", "cocktail.html.PasswordBox", "cocktail.html.TextArea", "woost.views.RichTextEditor" ] search_controls = ["cocktail.html.TextBox"] member_min = schema.Integer(listed_by_default=False, member_group="constraints") member_max = schema.Integer(min=member_min, listed_by_default=False, member_group="constraints") member_format = schema.String(listed_by_default=False, text_search=False, member_group="constraints") member_text_search = schema.Boolean(required=True, default=True, listed_by_default=False, member_group="behavior") member_normalized_index = schema.Boolean(required=True, default=True, listed_by_default=False, member_group="definition")
class UserInteger(UserMember): instantiable = True groups_order = UserMember.groups_order member_class = schema.Integer member_min = schema.Integer(listed_by_default=False, member_group="constraints") member_max = schema.Integer(min=member_min, listed_by_default=False, member_group="constraints")
class HTMLRenderer(Renderer): """A content renderer that handles XHTML/HTML pages.""" instantiable = True mime_types = set(["text/html", "text/xhtml", "application/xhtml"]) command = schema.String( required=True, default="python -m woost.models.rendering.renderurl " "%(source)s %(dest)s") window_width = schema.Integer(min=0) window_height = schema.Integer(min=0) def can_render(self, item, **parameters): return (isinstance(item, Publishable) and item.mime_type in self.mime_types and item.is_accessible( user=User.get_instance(qname="woost.anonymous_user"))) def render(self, item, window_width=None, window_height=None, **parameters): temp_path = mkdtemp() try: temp_image_file = os.path.join(temp_path, "thumbnail.png") command = self.command % { "source": item.get_uri(host="."), "dest": temp_image_file } if window_width is None: window_width = self.window_width if window_width is not None: command += " --min-width %d" % window_width if window_height is None: window_height = self.window_height if window_height is not None: command += " --min-width %d" % window_height Popen(command, shell=True).wait() return Image.open(temp_image_file) finally: rmtree(temp_path)
class VideoPlayerSettings(Item): instantiable = True visible_from_root = False player_initialization = TypeMapping() members_order = [ "title", "width", "height", "autoplay", "show_player_controls" ] title = schema.String( listed_by_default = False, required = True, unique = True, indexed = True, normalized_index = True, full_text_indexed = True, descriptive = True, translated = True ) width = schema.Integer( required = True, default = 480, listed_by_default = False ) height = schema.Integer( required = True, default = 385, listed_by_default = False ) autoplay = schema.Boolean( required = True, default = False, listed_by_default = False ) show_player_controls = schema.Boolean( required = True, default = True, listed_by_default = False ) def create_player(self, video): player = templates.new(video.video_player) player_initialization = self.player_initialization.get(video.__class__) if player_initialization: player_initialization(player, self, video) return player
class TwitterTimelineBlock(Block): instantiable = True type_group = "blocks.social" view_class = "cocktail.html.TwitterTimeline" members_order = [ "widget_id", "theme", "link_color", "width", "height", "related_accounts" ] widget_id = schema.String( required = True, member_group = "content" ) theme = schema.String( required = True, default = "light", enumeration = ("light", "dark"), member_group = "appearence" ) link_color = schema.Color( member_group = "appearence" ) width = schema.Integer( member_group = "appearence" ) height = schema.Integer( member_group = "appearence" ) related_accounts = schema.String( member_group = "tweet" ) def init_view(self, view): Block.init_view(self, view) view.widget_id = self.widget_id view.theme = self.theme view.link_color = self.link_color view.width = self.width view.height = self.height view.related = self.related_accounts
class UserDateTime(UserMember): instantiable = True groups_order = UserMember.groups_order member_class = schema.DateTime edit_controls = ["cocktail.html.TextBox", "cocktail.html.DatePicker"] search_controls = ["cocktail.html.TextBox", "cocktail.html.DatePicker"] member_min = schema.Integer(listed_by_default=False, member_group="constraints") member_max = schema.Integer(min=member_min, listed_by_default=False, member_group="constraints")
class IconRenderer(Renderer): instantiable = True members_order = ["icon_size", "icon_resolver_expression"] icon_size = schema.Integer(required=True, enumeration=[16, 32], edit_control="cocktail.html.RadioSelector") icon_resolver_expression = schema.String() def can_render(self, item, **parameters): return True def render(self, item, **parameters): return self.icon_resolver.find_icon_path(item, self.icon_size) @property def icon_resolver(self): try: return self._v_icon_resolver except AttributeError: expr = self.icon_resolver_expression icon_resolver = resolve(expr) if expr else app.icon_resolver self._v_icon_resolver = icon_resolver return icon_resolver @event_handler def handle_changed(cls, e): if e.member is cls.icon_resolver_expression: e.source._v_icon_resolver = None
def __init__(self, *args, **kwargs): schema.Schema.__init__(self, *args, **kwargs) file_name_kw = {} file_size_kw = {"min": 0} mime_type_kw = {} file_name_properties = kwargs.get("file_name_properties") if file_name_properties: file_name_kw.update(file_name_properties) file_size_properties = kwargs.get("file_size_properties") if file_size_properties: file_size_kw.update(file_size_properties) file_size_kw.setdefault( "translate_value", lambda value, language=None, **kwargs: format_bytes(value) if value or value == 0 else "") mime_type_properties = kwargs.get("mime_type_properties") if mime_type_properties: mime_type_kw.update(mime_type_properties) self.add_member(schema.String("file_name", **file_name_kw)) self.add_member(schema.Integer("file_size", **file_size_kw)) self.add_member(schema.String("mime_type", **mime_type_kw))
class MenuBlock(Block): instantiable = True view_class = "woost.views.Menu" root = schema.Reference(type=Document, related_end=schema.Collection(), member_group="content") root_visible = schema.Boolean(required=True, default=False, member_group="content") max_depth = schema.Integer(member_group="content") expanded = schema.Boolean(required=True, default=False, member_group="content") def init_view(self, view): Block.init_view(self, view) view.root = self.root view.root_visible = self.root_visible view.max_depth = self.max_depth view.expanded = self.expanded
def expanded_items(self): if self.params.read(schema.String("expanded")) == "all": return "all" else: return self.params.read( schema.Collection("expanded", type=set, items=schema.Integer(), default=set()))
class IssuuBlock(Block): instantiable = True view_class = "woost.extensions.issuu.IssuuBlockView" block_display = "woost.extensions.issuu.IssuuBlockDisplay" members_order = ["element_type", "issuu_document", "width", "height"] element_type = ElementType(member_group="content") issuu_document = schema.Reference(type=IssuuDocument, required=True, related_end=schema.Collection(), listed_by_default=False, member_group="content") width = schema.Integer(listed_by_default=False, member_group="content") height = schema.Integer(listed_by_default=False, member_group="content")
class Shadow(ImageEffect): instantiable = True offset = schema.Integer(required=True, default=5) padding = schema.Integer(required=True, default=8) color = schema.Color(required=True, default="#444444") iterations = 3 def apply(self, image): # Create the backdrop image -- a box in the background colour with a # shadow on it. total_width = image.size[0] + abs(self.offset) + 2 * self.padding total_height = image.size[1] + abs(self.offset) + 2 * self.padding back = Image.new("RGBA", (total_width, total_height)) # Place the shadow, taking into account the offset from the image shadow_left = self.padding + max(self.offset, 0) shadow_top = self.padding + max(self.offset, 0) color = resolve_color(self.color) back.paste(self.color, [ shadow_left, shadow_top, shadow_left + image.size[0], shadow_top + image.size[1] ]) # Apply the filter to blur the edges of the shadow. Since a small # kernel is used, the filter must be applied repeatedly to get a # decent blur. for n in range(self.iterations): back = back.filter(ImageFilter.BLUR) # Paste the input image onto the shadow backdrop image_left = self.padding - min(self.offset, 0) image_top = self.padding - min(self.offset, 0) back.paste(image, (image_left, image_top), image if image.mode in ("1", "L", "RGBA") else None) return back
class Rotate(ImageEffect): instantiable = True angle = schema.Integer(required=True) filter = Image.BICUBIC def apply(self, image): if image.mode != "RGBA": image = image.convert("RGBA") return image.rotate(self.angle, self.filter, True)
class PDFRenderer(Renderer): """A content renderer that handles pdf files.""" instantiable = True command = schema.String(required=True) timeout = schema.Integer(required=True, default=20) timeout_size_factor = schema.Float(default=10.0) def can_render(self, item, **parameters): return (self.command and isinstance(item, File) and item.resource_type == "document" and item.file_name.split(".")[-1].lower() == "pdf") def render(self, item, page=1, **parameters): timeout = self.timeout # Increase the timeout for bigger files if self.timeout_size_factor: size = item.file_size if size: timeout += size / (self.timeout_size_factor * 1024 * 1024) RESOLUTION = 0.25 temp_path = mkdtemp() try: temp_image_file = os.path.join(temp_path, "thumbnail.png") command = self.command % { "source": item.file_path, "dest": temp_image_file, "page": page } p = Popen(command, shell=True, stdout=PIPE) start = time() while p.poll() is None: if time() - start > timeout: p.terminate() raise IOError("PDF rendering timeout: '%s' took more than " "%.2f seconds" % (command, timeout)) sleep(RESOLUTION) return Image.open(temp_image_file) finally: rmtree(temp_path)
class Frame(ImageEffect): instantiable = True members_order = [ "edge_width", "edge_color", "vertical_padding", "horizontal_padding", "background" ] edge_width = schema.Integer(required=True, min=1, default=1) edge_color = schema.Color(required=True, default="#000000") vertical_padding = ImageSize(required=True, default="0") horizontal_padding = ImageSize(required=True, default="0") background = schema.Color(required=True, default="#000000") def apply(self, image): edge_color = resolve_color(self.edge_color) background = resolve_color(self.background) # Create the canvas width, height = image.size vertical_padding = ImageSize.resolve_size(self.vertical_padding, height) vertical_offset = self.edge_width + vertical_padding horizontal_padding = ImageSize.resolve_size(self.horizontal_padding, width) horizontal_offset = self.edge_width + horizontal_padding canvas = Image.new( "RGBA", (width + horizontal_offset * 2, height + vertical_offset * 2)) # Paint the border edge = self.edge_width if edge: canvas.paste(edge_color, None) # Paint the padding color canvas.paste(background, (edge, edge, edge + width + horizontal_padding * 2, edge + height + vertical_padding * 2)) # Paste the original image over the frame canvas.paste(image, (horizontal_offset, vertical_offset), image if image.mode in ("1", "L", "RGBA") else None) return canvas
class FreeUnitsDiscount(Discount): instantiable = True members_order = ["paid_units", "free_units", "repeated"] members_order = [ "paid_units", "free_units", "repeated" ] paid_units = schema.Integer( required = True, min = 0 ) free_units = schema.Integer( required = True, min = 1 ) repeated = schema.Boolean( required = True, default = True ) def apply(self, item, costs): paid = self.paid_units free = self.free_units quantity = costs["paid_quantity"] if self.repeated: quantity -= (quantity / (paid + free)) * free elif quantity > paid: quantity = max(paid, quantity - free) costs["paid_quantity"] = quantity
def model(self): model = schema.Schema("SetQuantitiesForm", members = [ schema.Collection("quantity", items = schema.Integer(min = 1), length = len(Basket.get().purchases) ) ]) @extend(model["quantity"].items) def translate_error(member, error, language = None, **kwargs): if isinstance(error, schema.exceptions.MinValueError): return translations( "SetQuantitiesForm-MinValueError", language, **kwargs ) else: return call_base(error, language, **kwargs) return model
def _handle_declared(event): cls = event.source # Add 'id' as an alias for custom primary members if cls.primary_member: if cls.primary_member.schema is cls \ and cls.primary_member.name != "id": cls.id = cls.__dict__[cls.primary_member.name] # Add an 'id' field to all indexed schemas that don't define their # own primary member explicitly. Will be initialized to an # incremental integer. elif cls.indexed: cls._generated_id = True cls.id = schema.Integer(name="id", primary=True, unique=True, required=True, indexed=True) cls.add_member(cls.id)
class ShopOrderEntry(Item): listed_from_root = False members_order = [ "shop_order", "product", "quantity" ] shop_order = schema.Reference( type = "woost.extensions.shop.shoporder.ShopOrder", bidirectional = True, required = True ) product = schema.Reference( type = "woost.extensions.shop.product.Product", bidirectional = True, required = True ) quantity = schema.Integer( required = True, min = 1 ) cost = schema.Decimal( required = True, default = Decimal("0"), editable = False ) def __translate__(self, language, **kwargs): return "%s (%d)" % ( translations(self.product, language), self.quantity )
class NewsListing(Block): max_page_size = 50 instantiable = True type_group = "blocks.listings" groups_order = list(Block.groups_order) groups_order.insert(groups_order.index("content") + 1, "listing") members_order = ["element_type", "paginated", "page_size", "view_class"] element_type = ElementType(member_group="content") paginated = schema.Boolean(required=True, default=False, member_group="listing") page_size = schema.Integer(min=1, required=paginated, member_group="listing") view_class = schema.String(required=True, shadows_attribute=True, enumeration=[ "woost.views.CompactNewsListing", "woost.views.TextOnlyNewsListing", "woost.views.TextAndImageNewsListing" ], default="woost.views.TextOnlyNewsListing", member_group="listing") def init_view(self, view): Block.init_view(self, view) view.tag = self.element_type view.name_prefix = self.name_prefix view.name_suffix = self.name_suffix view.depends_on(News) if self.paginated: view.pagination = self.pagination else: view.news = self.select_news() def select_news(self): news = News.select_accessible(order="-news_date") if not self.paginated and self.page_size: news.range = (0, self.page_size) return news @request_property def pagination(self): return get_parameter(self.pagination_member, errors="set_default", prefix=self.name_prefix, suffix=self.name_suffix) @request_property def pagination_member(self): return Pagination.copy( **{ "page_size.default": self.page_size, "page_size.max": self.max_page_size, "items": self.select_news() })
.. moduleauthor:: Martí Congost <*****@*****.**> """ from cocktail import schema from cocktail.translations import translations from woost.models import add_setting, Configuration from .locationtype import LocationType translations.load_bundle("woost.extensions.locations.settings") add_setting(schema.String("x_locations_service_uri", required=True, default="http://services.woost.info/locations", text_search=False), scopes=(Configuration, )) add_setting(schema.Integer("x_locations_update_frequency", min=1, default=15), scopes=(Configuration, )) add_setting(schema.Collection("x_locations_updated_location_types", default=[ "continent", "country", "autonomous_community", "province", "town" ], items=LocationType(required=True), text_search=False, ui_form_control="cocktail.ui.CheckList"), scopes=(Configuration, )) add_setting(schema.Collection("x_locations_updated_subset", items=schema.String(required=True), ui_form_control="cocktail.ui.TextArea"),
def position(self): return get_parameter( schema.Integer("position", min=0, max=len(self.collection)))
class FacebookLikeButton(Block): instantiable = True type_group = "blocks.social" view_class = "cocktail.html.FacebookLikeButton" members_order = [ "fb_href", "fb_layout", "fb_send", "fb_show_faces", "fb_width", "fb_action", "fb_font", "fb_colorscheme", "fb_ref" ] fb_href = schema.Reference(type=Publishable, related_end=schema.Collection(), member_group="content") fb_layout = schema.String( required=True, default="standard", enumeration=["standard", "button_count", "box_count"], member_group="appearence") fb_send = schema.Boolean(default=False, required=True, member_group="appearence") fb_show_faces = schema.Boolean(default=False, required=True, member_group="appearence") fb_width = schema.Integer(required=True, default=450, member_group="appearence") fb_action = schema.String(required=True, default="like", enumeration=["like", "recommend"], member_group="appearence") fb_font = schema.String(required=True, default="arial", enumeration=[ "arial", "lucida grande", "segoe ui", "tahoma", "trebuchet ms", "verdana" ], translatable_enumeration=False, member_group="appearence") fb_colorscheme = schema.String(required=True, default="light", enumeration=["light", "dark"], member_group="appearence") fb_ref = schema.String(member_group="content") def init_view(self, view): Block.init_view(self, view) view.href = self.fb_href and self.fb_href.get_uri(host=".") view.send = self.fb_send view.layout = self.fb_layout view.show_faces = self.fb_show_faces view.width = self.fb_width view.action = self.fb_action view.font = self.fb_font view.colorscheme = self.fb_colorscheme view.ref = self.fb_ref
class AudioEncoder(Item): instantiable = True visible_from_root = False resolution = 0.25 members_order = [ "identifier", "mime_type", "extension", "command", "timeout", "timeout_size_factor" ] identifier = schema.String(required=True, unique=True, indexed=True, normalized_index=False, descriptive=True) mime_type = schema.String(required=True) extension = schema.String(required=True) command = schema.String(required=True) timeout = schema.Integer(required=True, default=60) timeout_size_factor = schema.Float(default=10.0) def encode(self, file, dest): # Most encoders expect PCM wave files as input, so we start by finding # an appropiate decoder for the given file. mime_type = file.mime_type if mime_type == "audio/mp3": mime_type = "audio/mpeg" decoder = AudioDecoder.require_instance(mime_type=mime_type) # Next, we produce the command line instructions for the decoding / # encoding procedure, using a shell pipe. decode_command = decoder.command % file.file_path encode_command = self.command % dest command = decode_command + " | " + encode_command # Calculate the timeout, based on a base value incremented according to # a file size factor timeout = self.timeout if self.timeout_size_factor: size = file.file_size if size: timeout += size / (self.timeout_size_factor * 1024 * 1024) # Encode the file and save it to the indicated location proc = Popen(command, shell=True, stdout=PIPE, stderr=PIPE) start = time() while proc.poll() is None: if time() - start > timeout: proc.terminate() raise AudioEncodingTimeoutError( "The following audio encoding command exceeded its " "timeout of %d seconds: %s" % (timeout, command)) sleep(self.resolution) if proc.returncode: raise AudioEncodingCommandError( "The following audio encoding command reported an error: " "%s\n%s" % (command, proc.stderr.read().strip()))
class CountriesExtension(Extension): def __init__(self, **values): Extension.__init__(self, **values) self.extension_author = u"Whads/Accent SL" self.set( "description", u"""Proporciona accés a la llista de països del món, actualitzada a través d'Internet.""", "ca") self.set( "description", u"""Proporciona acceso a la lista de países del mundo, actualizada a través de Internet.""", "es") self.set( "description", u"""Provides a list of world countries, automatically updated from the Internet.""", "en") last_update = None update_frequency = schema.Integer(min=1, required=True, default=15) def _load(self): from woost.extensions.countries import country, strings now = time() if self.last_update is None \ or now - self.last_update >= self.update_frequency * SECONDS_IN_A_DAY: try: self.update_country_list() except: pass else: self.last_update = now datastore.commit() def update_country_list(self): from woost.extensions.countries.country import Country error = None database_modified = False service_uri = "http://www.lonelydrops.com/drops/1.0/list/%s/countries" data_expr = re.compile(r"var\s+drops_countries_[a-z]{2}\s*=\s*([^;]+)", re.DOTALL) json_property_normalization = re.compile( r"([a-zA-Z_$][a-zA-Z_$0-9]*)(?=\s*:)") for language in Configuration.instance.languages: try: javascript = urlopen(service_uri % language).read() except Exception, error: pass else: match = data_expr.search(javascript) if not match: sys.stderr.write( "The country list provider returned data in an " "unexpected format (requested translation: %s)" % language) continue json = match.group(1) json = json_property_normalization.sub(r'"\1"', json) try: data = loads(json) except Exception, error: sys.stderr.write( "The country list provider returned invalid data: %s" "(requested translation: %s)\n" % (error, language)) continue for country_data in data: # Ignore continents if country_data.get("isParent"): continue name = country_data["label"] iso_code = country_data["value"].lower() country = Country.get_instance(iso_code=iso_code) # New country if country is None: country = Country() country.iso_code = iso_code country.set("country_name", name, language) country.insert() database_modified = True # Modified country elif country.get("country_name", language) != name: country.set("country_name", name, language) database_modified = True
class Feed(Publishable): type_group = "setup" instantiable = True groups_order = ["meta", "feed_items"] members_order = [ "title", "ttl", "image", "description", "limit", "query_parameters", "item_title_expression", "item_link_expression", "item_publication_date_expression", "item_description_expression" ] default_mime_type = u"application/rss+xml" default_controller = schema.DynamicDefault( lambda: Controller.get_instance(qname="woost.feed_controller")) edit_controller = \ "woost.controllers.backoffice.feedfieldscontroller." \ "FeedFieldsController" edit_view = "woost.views.FeedFields" title = schema.String(indexed=True, normalized_index=True, full_text_indexed=True, descriptive=True, translated=True, member_group="meta") ttl = schema.Integer(listed_by_default=False, member_group="meta") image = schema.Reference( type=Publishable, related_end=schema.Collection(), relation_constraints=Publishable.resource_type.equal("image"), member_group="meta") description = schema.String(required=True, translated=True, listed_by_default=False, edit_control="cocktail.html.TextArea", member_group="meta") limit = schema.Integer(min=1, listed_by_default=False, member_group="feed_items") query_parameters = schema.Mapping(keys=schema.String(), required=True, listed_by_default=False, member_group="feed_items") item_title_expression = schema.CodeBlock(language="python", required=True, default="translations(item)", member_group="feed_items") item_link_expression = schema.CodeBlock(language="python", required=True, default="cms.uri(item)", member_group="feed_items") item_publication_date_expression = schema.CodeBlock( language="python", required=True, default="item.start_date or item.creation_time", member_group="feed_items") item_description_expression = schema.CodeBlock(language="python", required=True, default="item.description", member_group="feed_items") def select_items(self): user_collection = UserCollection(Publishable) user_collection.allow_paging = False user_collection.allow_member_selection = False user_collection.allow_language_selection = False user_collection.params.source = self.query_parameters.get user_collection.available_languages = \ Configuration.instance.get_enabled_languages() items = user_collection.subset if self.limit: items.range = (0, self.limit) return items
class File(Publishable): instantiable = True cacheable = False type_group = "resource" edit_node_class = \ "woost.controllers.backoffice.fileeditnode.FileEditNode" backoffice_heading_view = "woost.views.BackOfficeFileHeading" video_player = "cocktail.html.MediaElementVideo" default_mime_type = None default_encoding = None default_controller = schema.DynamicDefault( lambda: Controller.get_instance(qname="woost.file_controller")) members_order = ["title", "file_name", "file_size", "file_hash"] title = schema.String(indexed=True, normalized_index=True, full_text_indexed=True, descriptive=True, translated=True, member_group="content") file_name = schema.String(required=True, editable=False, member_group="content") file_size = schema.Integer( required=True, editable=False, translate_value=lambda size, language=None, **kwargs: "" if size in (None, "") else format_bytes(size), min=0, member_group="content") file_hash = schema.String(visible=False, searchable=False, text_search=False, member_group="content") @property def file_extension(self): return os.path.splitext(self.file_name)[1] @property def file_path(self): return app.path("upload", str(self.id)) @classmethod def from_path(cls, path, dest=None, languages=None, hash=None, encoding="utf-8", download_temp_folder=None, redownload=False): """Imports a file into the site. @param path: The path to the file that should be imported. @type path: str @param dest: The base path where the file should be copied (should match the upload folder for the application). @type dest: str @param languages: The set of languages that the created file will be translated into. @type languages: str set @return: The created file. @rtype: L{File} """ # The default behavior is to translate created files into all the languages # defined by the site if languages is None: from woost.models import Configuration languages = Configuration.instance.languages file_name = os.path.split(path)[1] title, ext = os.path.splitext(file_name) # Download remote files if "://" in path: if not download_temp_folder: download_temp_folder = mkdtemp() temp_path = os.path.join(download_temp_folder, file_name) if redownload or not os.path.exists(temp_path): response = urlopen(path) with open(temp_path, "w") as temp_file: copyfileobj(response, temp_file) path = temp_path if encoding: if isinstance(title, str): title = title.decode(encoding) if isinstance(file_name, str): file_name = file_name.decode(encoding) title = title.replace("_", " ").replace("-", " ") title = title[0].upper() + title[1:] file = cls() file.file_size = os.stat(path).st_size file.file_hash = hash or file_hash(path) file.file_name = file_name # Infer the file's MIME type mime_type = guess_type(file_name, strict=False) if mime_type: file.mime_type = mime_type[0] for language in languages: file.set("title", title, language) if dest is None: upload_path = file.file_path else: upload_path = os.path.join(dest, str(file.id)) copy(path, upload_path) return file