From 24c1d259ba4af084959d70c6a1f355d0a57f1191 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Rekucki?= Date: Sun, 11 Apr 2010 16:51:14 +0200 Subject: [PATCH] Coding style overhaul for Python files (PEP8 conformant). Removed buggy csstidy python rewrite. --- apps/compress/filter_base.py | 6 +- apps/compress/filters/csstidy/__init__.py | 12 +- .../filters/csstidy_python/__init__.py | 19 - .../filters/csstidy_python/csstidy.py | 636 ------------------ apps/compress/filters/csstidy_python/data.py | 421 ------------ .../filters/csstidy_python/optimizer.py | 383 ----------- .../compress/filters/csstidy_python/output.py | 101 --- apps/compress/filters/csstidy_python/tools.py | 109 --- apps/compress/filters/jsmin/__init__.py | 4 +- apps/compress/filters/jsmin/jsmin.py | 18 +- apps/compress/filters/yui/__init__.py | 3 +- .../management/commands/synccompress.py | 9 +- apps/compress/templatetags/compressed.py | 30 +- apps/compress/utils.py | 29 +- apps/compress/versioning/base.py | 7 +- apps/compress/versioning/hash/__init__.py | 20 +- apps/compress/versioning/mtime/__init__.py | 2 +- apps/django_cas/backends.py | 5 +- apps/django_cas/decorators.py | 1 + apps/django_cas/middleware.py | 1 + apps/django_cas/models.py | 2 +- apps/django_cas/views.py | 15 +- apps/filebrowser/base.py | 28 +- apps/filebrowser/decorators.py | 6 +- apps/filebrowser/fb_settings.py | 6 +- apps/filebrowser/fields.py | 38 +- apps/filebrowser/forms.py | 26 +- apps/filebrowser/functions.py | 48 +- apps/filebrowser/models.py | 2 +- apps/filebrowser/templatetags/fb_tags.py | 45 +- apps/filebrowser/templatetags/fb_versions.py | 46 +- apps/filebrowser/urls.py | 4 +- apps/filebrowser/views.py | 100 +-- apps/toolbar/admin.py | 4 +- apps/toolbar/management/__init__.py | 5 +- .../toolbar/management/commands/fixbuttons.py | 20 +- apps/toolbar/migrations/0001_initial.py | 16 +- ...ld_button_key_mod__chg_field_button_key.py | 14 +- .../0003_button_key_rename_to_accesskey.py | 16 +- apps/toolbar/models.py | 53 +- apps/toolbar/templatetags/toolbar_tags.py | 6 +- apps/wiki/forms.py | 75 ++- apps/wiki/helpers.py | 32 +- apps/wiki/models.py | 48 +- apps/wiki/nice_diff.py | 28 +- apps/wiki/tests.py | 11 +- apps/wiki/urls.py | 22 +- apps/wiki/views.py | 138 ++-- fabfile.py | 85 ++- lib/test_vstorage.py | 157 ++--- lib/vstorage.py | 106 +-- lib/wlapi.py | 55 +- platforma/context_processors.py | 9 +- platforma/manage.py | 6 +- platforma/settings.py | 58 +- platforma/urls.py | 4 +- scripts/crop.py | 9 +- scripts/imgconv.py | 14 +- setup.py | 97 +-- 59 files changed, 837 insertions(+), 2433 deletions(-) delete mode 100644 apps/compress/filters/csstidy_python/__init__.py delete mode 100644 apps/compress/filters/csstidy_python/csstidy.py delete mode 100644 apps/compress/filters/csstidy_python/data.py delete mode 100644 apps/compress/filters/csstidy_python/optimizer.py delete mode 100644 apps/compress/filters/csstidy_python/output.py delete mode 100644 apps/compress/filters/csstidy_python/tools.py diff --git a/apps/compress/filter_base.py b/apps/compress/filter_base.py index 9b98531b..3ce364f1 100644 --- a/apps/compress/filter_base.py +++ b/apps/compress/filter_base.py @@ -4,11 +4,13 @@ class FilterBase: def filter_css(self, css): raise NotImplementedError + def filter_js(self, js): raise NotImplementedError - + + class FilterError(Exception): """ This exception is raised when a filter fails """ - pass \ No newline at end of file + pass diff --git a/apps/compress/filters/csstidy/__init__.py b/apps/compress/filters/csstidy/__init__.py index d40e8eeb..de5dd09b 100644 --- a/apps/compress/filters/csstidy/__init__.py +++ b/apps/compress/filters/csstidy/__init__.py @@ -11,23 +11,25 @@ ARGUMENTS = getattr(settings, 'CSSTIDY_ARGUMENTS', '--template=highest') warnings.simplefilter('ignore', RuntimeWarning) + class CSSTidyFilter(FilterBase): + def filter_css(self, css): tmp_file = tempfile.NamedTemporaryFile(mode='w+b') tmp_file.write(css) tmp_file.flush() output_file = tempfile.NamedTemporaryFile(mode='w+b') - + command = '%s %s %s %s' % (BINARY, tmp_file.name, ARGUMENTS, output_file.name) - + command_output = os.popen(command).read() - + filtered_css = output_file.read() output_file.close() tmp_file.close() - + if self.verbose: print command_output - + return filtered_css diff --git a/apps/compress/filters/csstidy_python/__init__.py b/apps/compress/filters/csstidy_python/__init__.py deleted file mode 100644 index 7d581ed4..00000000 --- a/apps/compress/filters/csstidy_python/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.conf import settings - -from compress.filter_base import FilterBase -from compress.filters.csstidy_python.csstidy import CSSTidy - -COMPRESS_CSSTIDY_SETTINGS = getattr(settings, 'COMPRESS_CSSTIDY_SETTINGS', {}) - -class CSSTidyFilter(FilterBase): - def filter_css(self, css): - tidy = CSSTidy() - - for k, v in COMPRESS_CSSTIDY_SETTINGS.items(): - tidy.setSetting(k, v) - - tidy.parse(css) - - r = tidy.Output('string') - - return r diff --git a/apps/compress/filters/csstidy_python/csstidy.py b/apps/compress/filters/csstidy_python/csstidy.py deleted file mode 100644 index 6ae8dc73..00000000 --- a/apps/compress/filters/csstidy_python/csstidy.py +++ /dev/null @@ -1,636 +0,0 @@ -# CSSTidy - CSS Parse -# -# CSS Parser class -# -# This file is part of CSSTidy. -# -# CSSTidy is free software you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation either version 2 of the License, or -# (at your option) any later version. -# -# CSSTidy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with CSSTidy if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# @license http://opensource.org/licenses/gpl-license.php GNU Public License -# @package csstidy -# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006 - -import re - -from optimizer import CSSOptimizer -from output import CSSPrinter -import data -from tools import SortedDict - -class CSSTidy(object): - #Saves the parsed CSS - _css = "" - _raw_css = SortedDict() - _optimized_css = SortedDict() - - #List of Tokens - _tokens = [] - - #Printer class - _output = None - - #Optimiser class - _optimizer = None - - #Saves the CSS charset (@charset) - _charset = '' - - #Saves all @import URLs - _import = [] - - #Saves the namespace - _namespace = '' - - #Contains the version of csstidy - _version = '1.3' - - #Stores the settings - _settings = {} - - # Saves the parser-status. - # - # Possible values: - # - is = in selector - # - ip = in property - # - iv = in value - # - instr = in string (started at " or ' or ( ) - # - ic = in comment (ignore everything) - # - at = in @-block - _status = 'is' - - #Saves the current at rule (@media) - _at = '' - - #Saves the current selector - _selector = '' - - #Saves the current property - _property = '' - - #Saves the position of , in selectors - _sel_separate = [] - - #Saves the current value - _value = '' - - #Saves the current sub-value - _sub_value = '' - - #Saves all subvalues for a property. - _sub_value_arr = [] - - #Saves the char which opened the last string - _str_char = '' - _cur_string = '' - - #Status from which the parser switched to ic or instr - _from = '' - - #Variable needed to manage string-in-strings, for example url("foo.png") - _str_in_str = False - - #=True if in invalid at-rule - _invalid_at = False - - #=True if something has been added to the current selector - _added = False - - #Saves the message log - _log = SortedDict() - - #Saves the line number - _line = 1 - - def __init__(self): - self._settings['remove_bslash'] = True - self._settings['compress_colors'] = True - self._settings['compress_font-weight'] = True - self._settings['lowercase_s'] = False - self._settings['optimise_shorthands'] = 2 - self._settings['remove_last_'] = False - self._settings['case_properties'] = 1 - self._settings['sort_properties'] = False - self._settings['sort_selectors'] = False - self._settings['merge_selectors'] = 2 - self._settings['discard_invalid_properties'] = False - self._settings['css_level'] = 'CSS2.1' - self._settings['preserve_css'] = False - self._settings['timestamp'] = False - self._settings['template'] = 'highest_compression' - - #Maps self._status to methods - self.__statusMethod = {'is':self.__parseStatus_is, 'ip': self.__parseStatus_ip, 'iv':self.__parseStatus_iv, 'instr':self.__parseStatus_instr, 'ic':self.__parseStatus_ic, 'at':self.__parseStatus_at} - - self._output = CSSPrinter(self) - self._optimizer = CSSOptimizer(self) - - #Public Methods - def getSetting(self, setting): - return self._settings.get(setting, False) - - #Set the value of a setting. - def setSetting(self, setting, value): - self._settings[setting] = value - return True - - def log(self, message, ttype, line = -1): - if line == -1: - line = self._line - - line = int(line) - - add = {'m': message, 't': ttype} - - if not self._log.has_key(line): - self._log[line] = [] - self._log[line].append(add) - elif add not in self._log[line]: - self._log[line].append(add) - - - #Checks if a character is escaped (and returns True if it is) - def escaped(self, string, pos): - return not (string[pos-1] != '\\' or self.escaped(string, pos-1)) - - #Adds CSS to an existing media/selector - def merge_css_blocks(self, media, selector, css_add): - for prop, value in css_add.iteritems(): - self.__css_add_property(media, selector, prop, value, False) - - #Checks if $value is !important. - def is_important(self, value): - return '!important' in value.lower() - - #Returns a value without !important - def gvw_important(self, value): - if self.is_important(value): - ret = value.strip() - ret = ret[0:-9] - ret = ret.strip() - ret = ret[0:-1] - ret = ret.strip() - return ret - - return value - - def parse(self, cssString): - #Switch from \r\n to \n - self._css = cssString.replace("\r\n", "\n") + ' ' - self._raw_css = {} - self._optimized_css = {} - self._curComment = '' - - #Start Parsing - i = 0 - while i < len(cssString): - if self._css[i] == "\n" or self._css[i] == "\r": - self._line += 1 - - i += self.__statusMethod[self._status](i) - - i += 1; - - self._optimized_css = self._optimizer.optimize(self._raw_css) - - def parseFile(self, filename): - try: - f = open(filename, "r") - self.parse(f.read()) - finally: - f.close() - - #Private Methods - def __parseStatus_is(self, idx): - """ - Parse in Selector - """ - ret = 0 - - if self.__is_token(self._css, idx): - if self._css[idx] == '/' and self._css[idx+1] == '*' and self._selector.strip() == '': - self._status = 'ic' - self._from = 'is' - return 1 - - elif self._css[idx] == '@' and self._selector.strip() == '': - #Check for at-rule - self._invalid_at = True - - for name, ttype in data.at_rules.iteritems(): - if self._css[idx+1:len(name)].lower() == name.lower(): - if ttype == 'at': - self._at = '@' + name - else: - self._selector = '@' + name - - self._status = ttype - self._invalid_at = False - ret += len(name) - - if self._invalid_at: - self._selector = '@' - invalid_at_name = '' - for j in xrange(idx+1, len(self._css)): - if not self._css[j].isalpha(): - break; - - invalid_at_name += self._css[j] - - self.log('Invalid @-rule: ' + invalid_at_name + ' (removed)', 'Warning') - - elif self._css[idx] == '"' or self._css[idx] == "'": - self._cur_string = self._css[idx] - self._status = 'instr' - self._str_char = self._css[idx] - self._from = 'is' - - elif self._invalid_at and self._css[idx] == ';': - self._invalid_at = False - self._status = 'is' - - elif self._css[idx] == '{': - self._status = 'ip' - self.__add_token(data.SEL_START, self._selector) - self._added = False; - - elif self._css[idx] == '}': - self.__add_token(data.AT_END, self._at) - self._at = '' - self._selector = '' - self._sel_separate = [] - - elif self._css[idx] == ',': - self._selector = self._selector.strip() + ',' - self._sel_separate.append(len(self._selector)) - - elif self._css[idx] == '\\': - self._selector += self.__unicode(idx) - - #remove unnecessary universal selector, FS#147 - elif not (self._css[idx] == '*' and self._css[idx+1] in ('.', '#', '[', ':')): - self._selector += self._css[idx] - - else: - lastpos = len(self._selector)-1 - - if lastpos == -1 or not ((self._selector[lastpos].isspace() or self.__is_token(self._selector, lastpos) and self._selector[lastpos] == ',') and self._css[idx].isspace()): - self._selector += self._css[idx] - - return ret - - def __parseStatus_ip(self, idx): - """ - Parse in property - """ - if self.__is_token(self._css, idx): - if (self._css[idx] == ':' or self._css[idx] == '=') and self._property != '': - self._status = 'iv' - - if not self.getSetting('discard_invalid_properties') or self.__property_is_valid(self._property): - self.__add_token(data.PROPERTY, self._property) - - elif self._css[idx] == '/' and self._css[idx+1] == '*' and self._property == '': - self._status = 'ic' - self._from = 'ip' - return 1 - - elif self._css[idx] == '}': - self.__explode_selectors() - self._status = 'is' - self._invalid_at = False - self.__add_token(data.SEL_END, self._selector) - self._selector = '' - self._property = '' - - elif self._css[idx] == ';': - self._property = '' - - elif self._css[idx] == '\\': - self._property += self.__unicode(idx) - - elif not self._css[idx].isspace(): - self._property += self._css[idx] - - return 0 - - def __parseStatus_iv(self, idx): - """ - Parse in value - """ - pn = (( self._css[idx] == "\n" or self._css[idx] == "\r") and self.__property_is_next(idx+1) or idx == len(self._css)) #CHECK# - if self.__is_token(self._css, idx) or pn: - if self._css[idx] == '/' and self._css[idx+1] == '*': - self._status = 'ic' - self._from = 'iv' - return 1 - - elif self._css[idx] == '"' or self._css[idx] == "'" or self._css[idx] == '(': - self._cur_string = self._css[idx] - self._str_char = ')' if self._css[idx] == '(' else self._css[idx] - self._status = 'instr' - self._from = 'iv' - - elif self._css[idx] == ',': - self._sub_value = self._sub_value.strip() + ',' - - elif self._css[idx] == '\\': - self._sub_value += self.__unicode(idx) - - elif self._css[idx] == ';' or pn: - if len(self._selector) > 0 and self._selector[0] == '@' and data.at_rules.has_key(self._selector[1:]) and data.at_rules[self._selector[1:]] == 'iv': - self._sub_value_arr.append(self._sub_value.strip()) - - self._status = 'is' - - if '@charset' in self._selector: - self._charset = self._sub_value_arr[0] - - elif '@namespace' in self._selector: - self._namespace = ' '.join(self._sub_value_arr) - - elif '@import' in self._selector: - self._import.append(' '.join(self._sub_value_arr)) - - - self._sub_value_arr = [] - self._sub_value = '' - self._selector = '' - self._sel_separate = [] - - else: - self._status = 'ip' - - elif self._css[idx] != '}': - self._sub_value += self._css[idx] - - if (self._css[idx] == '}' or self._css[idx] == ';' or pn) and self._selector != '': - if self._at == '': - self._at = data.DEFAULT_AT - - #case settings - if self.getSetting('lowercase_s'): - self._selector = self._selector.lower() - - self._property = self._property.lower() - - if self._sub_value != '': - self._sub_value_arr.append(self._sub_value) - self._sub_value = '' - - self._value = ' '.join(self._sub_value_arr) - - - self._selector = self._selector.strip() - - valid = self.__property_is_valid(self._property) - - if (not self._invalid_at or self.getSetting('preserve_css')) and (not self.getSetting('discard_invalid_properties') or valid): - self.__css_add_property(self._at, self._selector, self._property, self._value) - self.__add_token(data.VALUE, self._value) - - if not valid: - if self.getSetting('discard_invalid_properties'): - self.log('Removed invalid property: ' + self._property, 'Warning') - - else: - self.log('Invalid property in ' + self.getSetting('css_level').upper() + ': ' + self._property, 'Warning') - - self._property = ''; - self._sub_value_arr = [] - self._value = '' - - if self._css[idx] == '}': - self.__explode_selectors() - self.__add_token(data.SEL_END, self._selector) - self._status = 'is' - self._invalid_at = False - self._selector = '' - - elif not pn: - self._sub_value += self._css[idx] - - if self._css[idx].isspace(): - if self._sub_value != '': - self._sub_value_arr.append(self._sub_value) - self._sub_value = '' - - return 0 - - def __parseStatus_instr(self, idx): - """ - Parse in String - """ - if self._str_char == ')' and (self._css[idx] == '"' or self._css[idx] == "'") and not self.escaped(self._css, idx): - self._str_in_str = not self._str_in_str - - temp_add = self._css[idx] # ...and no not-escaped backslash at the previous position - if (self._css[idx] == "\n" or self._css[idx] == "\r") and not (self._css[idx-1] == '\\' and not self.escaped(self._css, idx-1)): - temp_add = "\\A " - self.log('Fixed incorrect newline in string', 'Warning') - - if not (self._str_char == ')' and self._css[idx].isspace() and not self._str_in_str): - self._cur_string += temp_add - - if self._css[idx] == self._str_char and not self.escaped(self._css, idx) and not self._str_in_str: - self._status = self._from - regex = re.compile(r'([\s]+)', re.I | re.U | re.S) - if regex.match(self._cur_string) is None and self._property != 'content': - if self._str_char == '"' or self._str_char == "'": - self._cur_string = self._cur_string[1:-1] - - elif len(self._cur_string) > 3 and (self._cur_string[1] == '"' or self._cur_string[1] == "'"): - self._cur_string = self._cur_string[0] + self._cur_string[2:-2] + self._cur_string[-1] - - if self._from == 'iv': - self._sub_value += self._cur_string - - elif self._from == 'is': - self._selector += self._cur_string - - return 0 - - def __parseStatus_ic(self, idx): - """ - Parse css In Comment - """ - if self._css[idx] == '*' and self._css[idx+1] == '/': - self._status = self._from - self.__add_token(data.COMMENT, self._curComment) - self._curComment = '' - return 1 - - else: - self._curComment += self._css[idx] - - return 0 - - def __parseStatus_at(self, idx): - """ - Parse in at-block - """ - if self.__is_token(string, idx): - if self._css[idx] == '/' and self._css[idx+1] == '*': - self._status = 'ic' - self._from = 'at' - return 1 - - elif self._css[i] == '{': - self._status = 'is' - self.__add_token(data.AT_START, self._at) - - elif self._css[i] == ',': - self._at = self._at.strip() + ',' - - elif self._css[i] == '\\': - self._at += self.__unicode(i) - else: - lastpos = len(self._at)-1 - if not (self._at[lastpos].isspace() or self.__is_token(self._at, lastpos) and self._at[lastpos] == ',') and self._css[i].isspace(): - self._at += self._css[i] - - return 0 - - def __explode_selectors(self): - #Explode multiple selectors - if self.getSetting('merge_selectors') == 1: - new_sels = [] - lastpos = 0; - self._sel_separate.append(len(self._selector)) - - for num in xrange(len(self._sel_separate)): - pos = self._sel_separate[num] - if num == (len(self._sel_separate)): #CHECK# - pos += 1 - - new_sels.append(self._selector[lastpos:(pos-lastpos-1)]) - lastpos = pos - - if len(new_sels) > 1: - for selector in new_sels: - self.merge_css_blocks(self._at, selector, self._raw_css[self._at][self._selector]) - - del self._raw_css[self._at][self._selector] - - self._sel_separate = [] - - #Adds a property with value to the existing CSS code - def __css_add_property(self, media, selector, prop, new_val): - if self.getSetting('preserve_css') or new_val.strip() == '': - return - - if not self._raw_css.has_key(media): - self._raw_css[media] = SortedDict() - - if not self._raw_css[media].has_key(selector): - self._raw_css[media][selector] = SortedDict() - - self._added = True - if self._raw_css[media][selector].has_key(prop): - if (self.is_important(self._raw_css[media][selector][prop]) and self.is_important(new_val)) or not self.is_important(self._raw_css[media][selector][prop]): - del self._raw_css[media][selector][prop] - self._raw_css[media][selector][prop] = new_val.strip() - - else: - self._raw_css[media][selector][prop] = new_val.strip() - - #Checks if the next word in a string from pos is a CSS property - def __property_is_next(self, pos): - istring = self._css[pos: len(self._css)] - pos = istring.find(':') - if pos == -1: - return False; - - istring = istring[:pos].strip().lower() - if data.all_properties.has_key(istring): - self.log('Added semicolon to the end of declaration', 'Warning') - return True - - return False; - - #Checks if a property is valid - def __property_is_valid(self, prop): - return (data.all_properties.has_key(prop) and data.all_properties[prop].find(self.getSetting('css_level').upper()) != -1) - - #Adds a token to self._tokens - def __add_token(self, ttype, cssdata, do=False): - if self.getSetting('preserve_css') or do: - if ttype == data.COMMENT: - token = [ttype, cssdata] - else: - token = [ttype, cssdata.strip()] - - self._tokens.append(token) - - #Parse unicode notations and find a replacement character - def __unicode(self, idx): - ##FIX## - return '' - - #Starts parsing from URL - ##USED? - def __parse_from_url(self, url): - try: - if "http" in url.lower() or "https" in url.lower(): - f = urllib.urlopen(url) - else: - f = open(url) - - data = f.read() - return self.parse(data) - finally: - f.close() - - #Checks if there is a token at the current position - def __is_token(self, string, idx): - return (string[idx] in data.tokens and not self.escaped(string, idx)) - - - #Property Methods - def _getOutput(self): - self._output.prepare(self._optimized_css) - return self._output.render - - def _getLog(self): - ret = "" - ks = self._log.keys() - ks.sort() - for line in ks: - for msg in self._log[line]: - ret += "Type: " + msg['t'] + "\n" - ret += "Message: " + msg['m'] + "\n" - ret += "\n" - - return ret - - def _getCSS(self): - return self._css - - - #Properties - Output = property(_getOutput, None) - Log = property(_getLog, None) - CSS = property(_getCSS, None) - - -if __name__ == '__main__': - import sys - tidy = CSSTidy() - f = open(sys.argv[1], "r") - css = f.read() - f.close() - tidy.parse(css) - tidy.Output('file', filename="Stylesheet.min.css") - print tidy.Output() - #print tidy._import \ No newline at end of file diff --git a/apps/compress/filters/csstidy_python/data.py b/apps/compress/filters/csstidy_python/data.py deleted file mode 100644 index bd728cba..00000000 --- a/apps/compress/filters/csstidy_python/data.py +++ /dev/null @@ -1,421 +0,0 @@ -# Various CSS Data for CSSTidy -# -# This file is part of CSSTidy. -# -# CSSTidy is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# CSSTidy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with CSSTidy; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# @license http://opensource.org/licenses/gpl-license.php GNU Public License -# @package csstidy -# @author Florian Schmitz (floele at gmail dot com) 2005 - -AT_START = 1 -AT_END = 2 -SEL_START = 3 -SEL_END = 4 -PROPERTY = 5 -VALUE = 6 -COMMENT = 7 -DEFAULT_AT = 41 - -# All whitespace allowed in CSS -# -# @global array whitespace -# @version 1.0 -whitespace = frozenset([' ',"\n","\t","\r","\x0B"]) - -# All CSS tokens used by csstidy -# -# @global string tokens -# @version 1.0 -tokens = '/@}{;:=\'"(,\\!$%&)#+.<>?[]^`|~' - -# All CSS units (CSS 3 units included) -# -# @see compress_numbers() -# @global array units -# @version 1.0 -units = frozenset(['in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz']) - -# Available at-rules -# -# @global array at_rules -# @version 1.0 -at_rules = {'page':'is', 'font-face':'is', 'charset':'iv', 'import':'iv', 'namespace':'iv', 'media':'at'} - -# Properties that need a value with unit -# -# @todo CSS3 properties -# @see compress_numbers() -# @global array unit_values -# @version 1.2 -unit_values = frozenset(['background', 'background-position', 'border', 'border-top', 'border-right', 'border-bottom', - 'border-left', 'border-width', 'border-top-width', 'border-right-width', 'border-left-width', - 'border-bottom-width', 'bottom', 'border-spacing', 'font-size','height', 'left', 'margin', 'margin-top', - 'margin-right', 'margin-bottom', 'margin-left', 'max-height', 'max-width', 'min-height', 'min-width', - 'outline-width', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left','position', - 'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width' - ]) - - -# Properties that allow as value -# -# @todo CSS3 properties -# @see compress_numbers() -# @global array color_values -# @version 1.0 -color_values = frozenset(['background-color', 'border-color', 'border-top-color', 'border-right-color', - 'border-bottom-color', 'border-left-color', 'color', 'outline-color']) - - -# Default values for the background properties -# -# @todo Possibly property names will change during CSS3 development -# @global array background_prop_default -# @see dissolve_short_bg() -# @see merge_bg() -# @version 1.0 -background_prop_default = {} -background_prop_default['background-image'] = 'none' -background_prop_default['background-size'] = 'auto' -background_prop_default['background-repeat'] = 'repeat' -background_prop_default['background-position'] = '0 0' -background_prop_default['background-attachment'] = 'scroll' -background_prop_default['background-clip'] = 'border' -background_prop_default['background-origin'] = 'padding' -background_prop_default['background-color'] = 'transparent' - -# A list of non-W3C color names which get replaced by their hex-codes -# -# @global array replace_colors -# @see cut_color() -# @version 1.0 -replace_colors = {} -replace_colors['aliceblue'] = '#F0F8FF' -replace_colors['antiquewhite'] = '#FAEBD7' -replace_colors['aquamarine'] = '#7FFFD4' -replace_colors['azure'] = '#F0FFFF' -replace_colors['beige'] = '#F5F5DC' -replace_colors['bisque'] = '#FFE4C4' -replace_colors['blanchedalmond'] = '#FFEBCD' -replace_colors['blueviolet'] = '#8A2BE2' -replace_colors['brown'] = '#A52A2A' -replace_colors['burlywood'] = '#DEB887' -replace_colors['cadetblue'] = '#5F9EA0' -replace_colors['chartreuse'] = '#7FFF00' -replace_colors['chocolate'] = '#D2691E' -replace_colors['coral'] = '#FF7F50' -replace_colors['cornflowerblue'] = '#6495ED' -replace_colors['cornsilk'] = '#FFF8DC' -replace_colors['crimson'] = '#DC143C' -replace_colors['cyan'] = '#00FFFF' -replace_colors['darkblue'] = '#00008B' -replace_colors['darkcyan'] = '#008B8B' -replace_colors['darkgoldenrod'] = '#B8860B' -replace_colors['darkgray'] = '#A9A9A9' -replace_colors['darkgreen'] = '#006400' -replace_colors['darkkhaki'] = '#BDB76B' -replace_colors['darkmagenta'] = '#8B008B' -replace_colors['darkolivegreen'] = '#556B2F' -replace_colors['darkorange'] = '#FF8C00' -replace_colors['darkorchid'] = '#9932CC' -replace_colors['darkred'] = '#8B0000' -replace_colors['darksalmon'] = '#E9967A' -replace_colors['darkseagreen'] = '#8FBC8F' -replace_colors['darkslateblue'] = '#483D8B' -replace_colors['darkslategray'] = '#2F4F4F' -replace_colors['darkturquoise'] = '#00CED1' -replace_colors['darkviolet'] = '#9400D3' -replace_colors['deeppink'] = '#FF1493' -replace_colors['deepskyblue'] = '#00BFFF' -replace_colors['dimgray'] = '#696969' -replace_colors['dodgerblue'] = '#1E90FF' -replace_colors['feldspar'] = '#D19275' -replace_colors['firebrick'] = '#B22222' -replace_colors['floralwhite'] = '#FFFAF0' -replace_colors['forestgreen'] = '#228B22' -replace_colors['gainsboro'] = '#DCDCDC' -replace_colors['ghostwhite'] = '#F8F8FF' -replace_colors['gold'] = '#FFD700' -replace_colors['goldenrod'] = '#DAA520' -replace_colors['greenyellow'] = '#ADFF2F' -replace_colors['honeydew'] = '#F0FFF0' -replace_colors['hotpink'] = '#FF69B4' -replace_colors['indianred'] = '#CD5C5C' -replace_colors['indigo'] = '#4B0082' -replace_colors['ivory'] = '#FFFFF0' -replace_colors['khaki'] = '#F0E68C' -replace_colors['lavender'] = '#E6E6FA' -replace_colors['lavenderblush'] = '#FFF0F5' -replace_colors['lawngreen'] = '#7CFC00' -replace_colors['lemonchiffon'] = '#FFFACD' -replace_colors['lightblue'] = '#ADD8E6' -replace_colors['lightcoral'] = '#F08080' -replace_colors['lightcyan'] = '#E0FFFF' -replace_colors['lightgoldenrodyellow'] = '#FAFAD2' -replace_colors['lightgrey'] = '#D3D3D3' -replace_colors['lightgreen'] = '#90EE90' -replace_colors['lightpink'] = '#FFB6C1' -replace_colors['lightsalmon'] = '#FFA07A' -replace_colors['lightseagreen'] = '#20B2AA' -replace_colors['lightskyblue'] = '#87CEFA' -replace_colors['lightslateblue'] = '#8470FF' -replace_colors['lightslategray'] = '#778899' -replace_colors['lightsteelblue'] = '#B0C4DE' -replace_colors['lightyellow'] = '#FFFFE0' -replace_colors['limegreen'] = '#32CD32' -replace_colors['linen'] = '#FAF0E6' -replace_colors['magenta'] = '#FF00FF' -replace_colors['mediumaquamarine'] = '#66CDAA' -replace_colors['mediumblue'] = '#0000CD' -replace_colors['mediumorchid'] = '#BA55D3' -replace_colors['mediumpurple'] = '#9370D8' -replace_colors['mediumseagreen'] = '#3CB371' -replace_colors['mediumslateblue'] = '#7B68EE' -replace_colors['mediumspringgreen'] = '#00FA9A' -replace_colors['mediumturquoise'] = '#48D1CC' -replace_colors['mediumvioletred'] = '#C71585' -replace_colors['midnightblue'] = '#191970' -replace_colors['mintcream'] = '#F5FFFA' -replace_colors['mistyrose'] = '#FFE4E1' -replace_colors['moccasin'] = '#FFE4B5' -replace_colors['navajowhite'] = '#FFDEAD' -replace_colors['oldlace'] = '#FDF5E6' -replace_colors['olivedrab'] = '#6B8E23' -replace_colors['orangered'] = '#FF4500' -replace_colors['orchid'] = '#DA70D6' -replace_colors['palegoldenrod'] = '#EEE8AA' -replace_colors['palegreen'] = '#98FB98' -replace_colors['paleturquoise'] = '#AFEEEE' -replace_colors['palevioletred'] = '#D87093' -replace_colors['papayawhip'] = '#FFEFD5' -replace_colors['peachpuff'] = '#FFDAB9' -replace_colors['peru'] = '#CD853F' -replace_colors['pink'] = '#FFC0CB' -replace_colors['plum'] = '#DDA0DD' -replace_colors['powderblue'] = '#B0E0E6' -replace_colors['rosybrown'] = '#BC8F8F' -replace_colors['royalblue'] = '#4169E1' -replace_colors['saddlebrown'] = '#8B4513' -replace_colors['salmon'] = '#FA8072' -replace_colors['sandybrown'] = '#F4A460' -replace_colors['seagreen'] = '#2E8B57' -replace_colors['seashell'] = '#FFF5EE' -replace_colors['sienna'] = '#A0522D' -replace_colors['skyblue'] = '#87CEEB' -replace_colors['slateblue'] = '#6A5ACD' -replace_colors['slategray'] = '#708090' -replace_colors['snow'] = '#FFFAFA' -replace_colors['springgreen'] = '#00FF7F' -replace_colors['steelblue'] = '#4682B4' -replace_colors['tan'] = '#D2B48C' -replace_colors['thistle'] = '#D8BFD8' -replace_colors['tomato'] = '#FF6347' -replace_colors['turquoise'] = '#40E0D0' -replace_colors['violet'] = '#EE82EE' -replace_colors['violetred'] = '#D02090' -replace_colors['wheat'] = '#F5DEB3' -replace_colors['whitesmoke'] = '#F5F5F5' -replace_colors['yellowgreen'] = '#9ACD32' - -#A list of optimized colors -optimize_colors = {} -optimize_colors['black'] = '#000' -optimize_colors['fuchsia'] = '#F0F' -optimize_colors['white'] = '#FFF' -optimize_colors['yellow'] = '#FF0' -optimize_colors['cyan'] = '#0FF' -optimize_colors['magenta'] = '#F0F' -optimize_colors['lightslategray'] = '#789' - -optimize_colors['#800000'] = 'maroon' -optimize_colors['#FFA500'] = 'orange' -optimize_colors['#808000'] = 'olive' -optimize_colors['#800080'] = 'purple' -optimize_colors['#008000'] = 'green' -optimize_colors['#000080'] = 'navy' -optimize_colors['#008080'] = 'teal' -optimize_colors['#C0C0C0'] = 'silver' -optimize_colors['#808080'] = 'gray' -optimize_colors['#4B0082'] = 'indigo' -optimize_colors['#FFD700'] = 'gold' -optimize_colors['#A52A2A'] = 'brown' -optimize_colors['#00FFFF'] = 'cyan' -optimize_colors['#EE82EE'] = 'violet' -optimize_colors['#DA70D6'] = 'orchid' -optimize_colors['#FFE4C4'] = 'bisque' -optimize_colors['#F0E68C'] = 'khaki' -optimize_colors['#F5DEB3'] = 'wheat' -optimize_colors['#FF7F50'] = 'coral' -optimize_colors['#F5F5DC'] = 'beige' -optimize_colors['#F0FFFF'] = 'azure' -optimize_colors['#A0522D'] = 'sienna' -optimize_colors['#CD853F'] = 'peru' -optimize_colors['#FFFFF0'] = 'ivory' -optimize_colors['#DDA0DD'] = 'plum' -optimize_colors['#D2B48C'] = 'tan' -optimize_colors['#FFC0CB'] = 'pink' -optimize_colors['#FFFAFA'] = 'snow' -optimize_colors['#FA8072'] = 'salmon' -optimize_colors['#FF6347'] = 'tomato' -optimize_colors['#FAF0E6'] = 'linen' -optimize_colors['#F00'] = 'red' - - -# A list of all shorthand properties that are devided into four properties and/or have four subvalues -# -# @global array shorthands -# @todo Are there new ones in CSS3? -# @see dissolve_4value_shorthands() -# @see merge_4value_shorthands() -# @version 1.0 -shorthands = {} -shorthands['border-color'] = ['border-top-color','border-right-color','border-bottom-color','border-left-color'] -shorthands['border-style'] = ['border-top-style','border-right-style','border-bottom-style','border-left-style'] -shorthands['border-width'] = ['border-top-width','border-right-width','border-bottom-width','border-left-width'] -shorthands['margin'] = ['margin-top','margin-right','margin-bottom','margin-left'] -shorthands['padding'] = ['padding-top','padding-right','padding-bottom','padding-left'] -shorthands['-moz-border-radius'] = 0 - -# All CSS Properties. Needed for csstidy::property_is_next() -# -# @global array all_properties -# @todo Add CSS3 properties -# @version 1.0 -# @see csstidy::property_is_next() -all_properties = {} -all_properties['background'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['background-color'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['background-image'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['background-position'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-top'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-right'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-left'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-color'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-top-color'] = 'CSS2.0,CSS2.1' -all_properties['border-bottom-color'] = 'CSS2.0,CSS2.1' -all_properties['border-left-color'] = 'CSS2.0,CSS2.1' -all_properties['border-right-color'] = 'CSS2.0,CSS2.1' -all_properties['border-style'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-top-style'] = 'CSS2.0,CSS2.1' -all_properties['border-right-style'] = 'CSS2.0,CSS2.1' -all_properties['border-left-style'] = 'CSS2.0,CSS2.1' -all_properties['border-bottom-style'] = 'CSS2.0,CSS2.1' -all_properties['border-width'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['border-collapse'] = 'CSS2.0,CSS2.1' -all_properties['border-spacing'] = 'CSS2.0,CSS2.1' -all_properties['bottom'] = 'CSS2.0,CSS2.1' -all_properties['caption-side'] = 'CSS2.0,CSS2.1' -all_properties['content'] = 'CSS2.0,CSS2.1' -all_properties['clear'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['clip'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['color'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['counter-reset'] = 'CSS2.0,CSS2.1' -all_properties['counter-increment'] = 'CSS2.0,CSS2.1' -all_properties['cursor'] = 'CSS2.0,CSS2.1' -all_properties['empty-cells'] = 'CSS2.0,CSS2.1' -all_properties['display'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['direction'] = 'CSS2.0,CSS2.1' -all_properties['float'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['font'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['font-family'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['font-style'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['font-stretch'] = 'CSS2.0' -all_properties['font-size-adjust'] = 'CSS2.0' -all_properties['font-size'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['height'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['left'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['line-height'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['list-style'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['margin'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['marks'] = 'CSS1.0,CSS2.0' -all_properties['marker-offset'] = 'CSS2.0' -all_properties['max-height'] = 'CSS2.0,CSS2.1' -all_properties['max-width'] = 'CSS2.0,CSS2.1' -all_properties['min-height'] = 'CSS2.0,CSS2.1' -all_properties['min-width'] = 'CSS2.0,CSS2.1' -all_properties['overflow'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['orphans'] = 'CSS2.0,CSS2.1' -all_properties['outline'] = 'CSS2.0,CSS2.1' -all_properties['outline-width'] = 'CSS2.0,CSS2.1' -all_properties['outline-style'] = 'CSS2.0,CSS2.1' -all_properties['outline-color'] = 'CSS2.0,CSS2.1' -all_properties['padding'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['page-break-inside'] = 'CSS2.0,CSS2.1' -all_properties['page'] = 'CSS2.0' -all_properties['position'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['quotes'] = 'CSS2.0,CSS2.1' -all_properties['right'] = 'CSS2.0,CSS2.1' -all_properties['size'] = 'CSS1.0,CSS2.0' -all_properties['speak-header'] = 'CSS2.0,CSS2.1' -all_properties['table-layout'] = 'CSS2.0,CSS2.1' -all_properties['top'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['text-align'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['text-shadow'] = 'CSS2.0' -all_properties['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['white-space'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['unicode-bidi'] = 'CSS2.0,CSS2.1' -all_properties['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['visibility'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['width'] = 'CSS1.0,CSS2.0,CSS2.1' -all_properties['widows'] = 'CSS2.0,CSS2.1' -all_properties['z-index'] = 'CSS1.0,CSS2.0,CSS2.1' - -# Speech # -all_properties['volume'] = 'CSS2.0,CSS2.1' -all_properties['speak'] = 'CSS2.0,CSS2.1' -all_properties['pause'] = 'CSS2.0,CSS2.1' -all_properties['pause-before'] = 'CSS2.0,CSS2.1' -all_properties['pause-after'] = 'CSS2.0,CSS2.1' -all_properties['cue'] = 'CSS2.0,CSS2.1' -all_properties['cue-before'] = 'CSS2.0,CSS2.1' -all_properties['cue-after'] = 'CSS2.0,CSS2.1' -all_properties['play-during'] = 'CSS2.0,CSS2.1' -all_properties['azimuth'] = 'CSS2.0,CSS2.1' -all_properties['elevation'] = 'CSS2.0,CSS2.1' -all_properties['speech-rate'] = 'CSS2.0,CSS2.1' -all_properties['voice-family'] = 'CSS2.0,CSS2.1' -all_properties['pitch'] = 'CSS2.0,CSS2.1' -all_properties['pitch-range'] = 'CSS2.0,CSS2.1' -all_properties['stress'] = 'CSS2.0,CSS2.1' -all_properties['richness'] = 'CSS2.0,CSS2.1' -all_properties['speak-punctuation'] = 'CSS2.0,CSS2.1' -all_properties['speak-numeral'] = 'CSS2.0,CSS2.1' \ No newline at end of file diff --git a/apps/compress/filters/csstidy_python/optimizer.py b/apps/compress/filters/csstidy_python/optimizer.py deleted file mode 100644 index 7cd284cf..00000000 --- a/apps/compress/filters/csstidy_python/optimizer.py +++ /dev/null @@ -1,383 +0,0 @@ -# CSSTidy - CSS Optimizer -# -# CSS Optimizer class -# -# This file is part of CSSTidy. -# -# CSSTidy is free software you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation either version 2 of the License, or -# (at your option) any later version. -# -# CSSTidy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with CSSTidy if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# @license http://opensource.org/licenses/gpl-license.php GNU Public License -# @package csstidy -# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006 - -import data -from tools import SortedDict - - -class CSSOptimizer(object): - def __init__(self, parser): - #raw_css is a dict - self.parser = parser - self._optimized_css = SortedDict - - -#PUBLIC METHODS - def optimize(self, raw_css): - if self.parser.getSetting('preserve_css'): - return raw_css - - self._optimized_css = raw_css - - if self.parser.getSetting('merge_selectors') == 2: - self.__merge_selectors() - - ##OPTIMIZE## - for media, css in self._optimized_css.iteritems(): - for selector, cssdata in css.iteritems(): - if self.parser.getSetting('optimise_shorthands') >= 1: - cssdata = self.__merge_4value_shorthands(cssdata) - - if self.parser.getSetting('optimise_shorthands') >= 2: - cssdata = self.__merge_bg(cssdata) - - for item, value in cssdata.iteritems(): - value = self.__compress_numbers(item, value) - value = self.__compress_important(value) - - if item in data.color_values and self.parser.getSetting('compress_colors'): - old = value[:] - value = self.__compress_color(value) - if old != value: - self.parser.log('In "' + selector + '" Optimised ' + item + ': Changed ' + old + ' to ' + value, 'Information') - - if item == 'font-weight' and self.parser.getSetting('compress_font-weight'): - if value == 'bold': - value = '700' - self.parser.log('In "' + selector + '" Optimised font-weight: Changed "bold" to "700"', 'Information') - - elif value == 'normal': - value = '400' - self.parser.log('In "' + selector + '" Optimised font-weight: Changed "normal" to "400"', 'Information') - - self._optimized_css[media][selector][item] = value - - - return self._optimized_css - - -#PRIVATE METHODS - def __merge_bg(self, cssdata): - """ - Merges all background properties - @cssdata (dict) is a dictionary of the selector properties - """ - #Max number of background images. CSS3 not yet fully implemented - img = 1 - clr = 1 - bg_img_list = [] - if cssdata.has_key('background-image'): - img = len(cssdata['background-image'].split(',')) - bg_img_list = self.parser.gvw_important(cssdata['background-image']).split(',') - - elif cssdata.has_key('background-color'): - clr = len(cssdata['background-color'].split(',')) - - - number_of_values = max(img, clr, 1) - - new_bg_value = '' - important = '' - - for i in xrange(number_of_values): - for bg_property, default_value in data.background_prop_default.iteritems(): - #Skip if property does not exist - if not cssdata.has_key(bg_property): - continue - - cur_value = cssdata[bg_property] - - #Skip some properties if there is no background image - if (len(bg_img_list) > i and bg_img_list[i] == 'none') and bg_property in frozenset(['background-size', 'background-position', 'background-attachment', 'background-repeat']): - continue - - #Remove !important - if self.parser.is_important(cur_value): - important = ' !important' - cur_value = self.parser.gvw_important(cur_value) - - #Do not add default values - if cur_value == default_value: - continue - - temp = cur_value.split(',') - - if len(temp) > i: - if bg_property == 'background-size': - new_bg_value += '(' + temp[i] + ') ' - - else: - new_bg_value += temp[i] + ' ' - - new_bg_value = new_bg_value.strip() - if i != (number_of_values-1): - new_bg_value += ',' - - #Delete all background-properties - for bg_property, default_value in data.background_prop_default.iteritems(): - try: - del cssdata[bg_property] - except: - pass - - #Add new background property - if new_bg_value != '': - cssdata['background'] = new_bg_value + important - - return cssdata - - def __merge_4value_shorthands(self, cssdata): - """ - Merges Shorthand properties again, the opposite of dissolve_4value_shorthands() - @cssdata (dict) is a dictionary of the selector properties - """ - for key, value in data.shorthands.iteritems(): - important = '' - if value != 0 and cssdata.has_key(value[0]) and cssdata.has_key(value[1]) and cssdata.has_key(value[2]) and cssdata.has_key(value[3]): - cssdata[key] = '' - - for i in xrange(4): - val = cssdata[value[i]] - if self.parser.is_important(val): - important = '!important' - cssdata[key] += self.parser.gvw_important(val) + ' ' - - else: - cssdata[key] += val + ' ' - - del cssdata[value[i]] - if cssdata.has_key(key): - cssdata[key] = self.__shorthand(cssdata[key] + important.strip()) - - return cssdata - - - def __merge_selectors(self): - """ - Merges selectors with same properties. Example: a{color:red} b{color:red} . a,b{color:red} - Very basic and has at least one bug. Hopefully there is a replacement soon. - @selector_one (string) is the current selector - @value_one (dict) is a dictionary of the selector properties - Note: Currently is the elements of a selector are identical, but in a different order, they are not merged - """ - - ##OPTIMIZE## - ##FIX## - - raw_css = self._optimized_css.copy() - delete = [] - add = SortedDict() - for media, css in raw_css.iteritems(): - for selector_one, value_one in css.iteritems(): - newsel = selector_one - - for selector_two, value_two in css.iteritems(): - if selector_one == selector_two: - #We need to skip self - continue - - if value_one == value_two: - #Ok, we need to merge these two selectors - newsel += ', ' + selector_two - delete.append((media, selector_two)) - - - if not add.has_key(media): - add[media] = SortedDict() - - add[media][newsel] = value_one - delete.append((media, selector_one)) - - for item in delete: - try: - del self._optimized_css[item[0]][item[1]] - except: - #Must have already been deleted - continue - - for media, css in add.iteritems(): - self._optimized_css[media].update(css) - - - - def __shorthand(self, value): - """ - Compresses shorthand values. Example: margin:1px 1px 1px 1px . margin:1px - @value (string) - """ - - ##FIX## - - important = ''; - if self.parser.is_important(value): - value_list = self.parser.gvw_important(value) - important = '!important' - else: - value_list = value - - ret = value - value_list = value_list.split(' ') - - if len(value_list) == 4: - if value_list[0] == value_list[1] and value_list[0] == value_list[2] and value_list[0] == value_list[3]: - ret = value_list[0] + important - - elif value_list[1] == value_list[3] and value_list[0] == value_list[2]: - ret = value_list[0] + ' ' + value_list[1] + important - - elif value_list[1] == value_list[3]: - ret = value_list[0] + ' ' + value_list[1] + ' ' + value_list[2] + important - - elif len(value_list) == 3: - if value_list[0] == value_list[1] and value_list[0] == value_list[2]: - ret = value_list[0] + important - - elif value_list[0] == value_list[2]: - return value_list[0] + ' ' + value_list[1] + important - - elif len(value_list) == 2: - if value_list[0] == value_list[1]: - ret = value_list[0] + important - - if ret != value: - self.parser.log('Optimised shorthand notation: Changed "' + value + '" to "' + ret + '"', 'Information') - - return ret - - def __compress_important(self, value): - """ - Removes unnecessary whitespace in ! important - @value (string) - """ - if self.parser.is_important(value): - value = self.parser.gvw_important(value) + '!important' - - return value - - def __compress_numbers(self, prop, value): - """ - Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 ) - @value (string) is the posible number to be compressed - """ - - ##FIX## - - value = value.split('/') - - for l in xrange(len(value)): - #continue if no numeric value - if not (len(value[l]) > 0 and (value[l][0].isdigit() or value[l][0] in ('+', '-') )): - continue - - #Fix bad colors - if prop in data.color_values: - value[l] = '#' + value[l] - - is_floatable = False - try: - float(value[l]) - is_floatable = True - except: - pass - - if is_floatable and float(value[l]) == 0: - value[l] = '0' - - elif value[l][0] != '#': - unit_found = False - for unit in data.units: - pos = value[l].lower().find(unit) - if pos != -1 and prop not in data.shorthands: - value[l] = self.__remove_leading_zeros(float(value[l][:pos])) + unit - unit_found = True - break; - - if not unit_found and prop in data.unit_values and prop not in data.shorthands: - value[l] = self.__remove_leading_zeros(float(value[l])) + 'px' - - elif not unit_found and prop not in data.shorthands: - value[l] = self.__remove_leading_zeros(float(value[l])) - - - if len(value) > 1: - return '/'.join(value) - - return value[0] - - def __remove_leading_zeros(self, float_val): - """ - Removes the leading zeros from a float value - @float_val (float) - @returns (string) - """ - #Remove leading zero - if abs(float_val) < 1: - if float_val < 0: - float_val = '-' . str(float_val)[2:] - else: - float_val = str(float_val)[1:] - - return str(float_val) - - def __compress_color(self, color): - """ - Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values. - @color (string) the {posible} color to change - """ - - #rgb(0,0,0) . #000000 (or #000 in this case later) - if color[:4].lower() == 'rgb(': - color_tmp = color[4:(len(color)-5)] - color_tmp = color_tmp.split(',') - - for c in color_tmp: - c = c.strip() - if c[:-1] == '%': - c = round((255*color_tmp[i])/100) - - if color_tmp[i] > 255: - color_tmp[i] = 255 - - color = '#' - - for i in xrange(3): - if color_tmp[i] < 16: - color += '0' + str(hex(color_tmp[i])).replace('0x', '') - else: - color += str(hex(color_tmp[i])).replace('0x', '') - - #Fix bad color names - if data.replace_colors.has_key(color.lower()): - color = data.replace_colors[color.lower()] - - #aabbcc . #abc - if len(color) == 7: - color_temp = color.lower() - if color_temp[0] == '#' and color_temp[1] == color_temp[2] and color_temp[3] == color_temp[4] and color_temp[5] == color_temp[6]: - color = '#' + color[1] + color[3] + color[5] - - if data.optimize_colors.has_key(color.lower()): - color = data.optimize_colors[color.lower()] - - return color \ No newline at end of file diff --git a/apps/compress/filters/csstidy_python/output.py b/apps/compress/filters/csstidy_python/output.py deleted file mode 100644 index 795a0d05..00000000 --- a/apps/compress/filters/csstidy_python/output.py +++ /dev/null @@ -1,101 +0,0 @@ -# CSSTidy - CSS Printer -# -# CSS Printer class -# -# This file is part of CSSTidy. -# -# CSSTidy is free software you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation either version 2 of the License, or -# (at your option) any later version. -# -# CSSTidy is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with CSSTidy if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -# -# @license http://opensource.org/licenses/gpl-license.php GNU Public License -# @package csstidy -# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006 - -import data - -class CSSPrinter(object): - def __init__(self, parser): - self.parser = parser - self._css = {} - self.__renderMethods = {'string': self.__renderString, 'file': self.__renderFile} - -#PUBLIC METHODS - def prepare(self, css): - self._css = css - - def render(self, output="string", *args, **kwargs): - return self.__renderMethods[output](*args, **kwargs) - -#PRIVATE METHODS - def __renderString(self, *args, **kwargs): - ##OPTIMIZE## - template = self.parser.getSetting('template') - ret = "" - - if template == 'highest_compression': - top_line_end = "" - iner_line_end = "" - bottom_line_end = "" - indent = "" - - elif template == 'high_compression': - top_line_end = "\n" - iner_line_end = "" - bottom_line_end = "\n" - indent = "" - - elif template == 'default': - top_line_end = "\n" - iner_line_end = "\n" - bottom_line_end = "\n\n" - indent = "" - - elif template == 'low_compression': - top_line_end = "\n" - iner_line_end = "\n" - bottom_line_end = "\n\n" - indent = " " - - if self.parser.getSetting('timestamp'): - ret += '/# CSSTidy ' + self.parser.version + ': ' + datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000") + ' #/' + top_line_end - - for item in self.parser._import: - ret += '@import(' + item + ');' + top_line_end - - for item in self.parser._charset: - ret += '@charset(' + item + ');' + top_line_end - - for item in self.parser._namespace: - ret += '@namespace(' + item + ');' + top_line_end - - for media, css in self._css.iteritems(): - for selector, cssdata in css.iteritems(): - ret += selector + '{' + top_line_end - - for item, value in cssdata.iteritems(): - ret += indent + item + ':' + value + ';' + iner_line_end - - ret += '}' + bottom_line_end - - return ret - - def __renderFile(self, filename=None, *args, **kwargs): - if filename is None: - return self.__renderString() - - try: - f = open(filename, "w") - f.write(self.__renderString()) - finally: - f.close() \ No newline at end of file diff --git a/apps/compress/filters/csstidy_python/tools.py b/apps/compress/filters/csstidy_python/tools.py deleted file mode 100644 index e62faef2..00000000 --- a/apps/compress/filters/csstidy_python/tools.py +++ /dev/null @@ -1,109 +0,0 @@ - -class SortedDict(dict): - """ - A dictionary that keeps its keys in the order in which they're inserted. - """ - def __init__(self, data=None): - if data is None: - data = {} - super(SortedDict, self).__init__(data) - if isinstance(data, dict): - self.keyOrder = data.keys() - else: - self.keyOrder = [] - for key, value in data: - if key not in self.keyOrder: - self.keyOrder.append(key) - - def __deepcopy__(self, memo): - from copy import deepcopy - return self.__class__([(key, deepcopy(value, memo)) - for key, value in self.iteritems()]) - - def __setitem__(self, key, value): - super(SortedDict, self).__setitem__(key, value) - if key not in self.keyOrder: - self.keyOrder.append(key) - - def __delitem__(self, key): - super(SortedDict, self).__delitem__(key) - self.keyOrder.remove(key) - - def __iter__(self): - for k in self.keyOrder: - yield k - - def pop(self, k, *args): - result = super(SortedDict, self).pop(k, *args) - try: - self.keyOrder.remove(k) - except ValueError: - # Key wasn't in the dictionary in the first place. No problem. - pass - return result - - def popitem(self): - result = super(SortedDict, self).popitem() - self.keyOrder.remove(result[0]) - return result - - def items(self): - return zip(self.keyOrder, self.values()) - - def iteritems(self): - for key in self.keyOrder: - yield key, super(SortedDict, self).__getitem__(key) - - def keys(self): - return self.keyOrder[:] - - def iterkeys(self): - return iter(self.keyOrder) - - def values(self): - return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder] - - def itervalues(self): - for key in self.keyOrder: - yield super(SortedDict, self).__getitem__(key) - - def update(self, dict_): - for k, v in dict_.items(): - self.__setitem__(k, v) - - def setdefault(self, key, default): - if key not in self.keyOrder: - self.keyOrder.append(key) - return super(SortedDict, self).setdefault(key, default) - - def value_for_index(self, index): - """Returns the value of the item at the given zero-based index.""" - return self[self.keyOrder[index]] - - def insert(self, index, key, value): - """Inserts the key, value pair before the item with the given index.""" - if key in self.keyOrder: - n = self.keyOrder.index(key) - del self.keyOrder[n] - if n < index: - index -= 1 - self.keyOrder.insert(index, key) - super(SortedDict, self).__setitem__(key, value) - - def copy(self): - """Returns a copy of this object.""" - # This way of initializing the copy means it works for subclasses, too. - obj = self.__class__(self) - obj.keyOrder = self.keyOrder[:] - return obj - - def __repr__(self): - """ - Replaces the normal dict.__repr__ with a version that returns the keys - in their sorted order. - """ - return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) - - def clear(self): - super(SortedDict, self).clear() - self.keyOrder = [] \ No newline at end of file diff --git a/apps/compress/filters/jsmin/__init__.py b/apps/compress/filters/jsmin/__init__.py index d2262008..340bef2b 100644 --- a/apps/compress/filters/jsmin/__init__.py +++ b/apps/compress/filters/jsmin/__init__.py @@ -1,6 +1,8 @@ from compress.filters.jsmin.jsmin import jsmin from compress.filter_base import FilterBase + class JSMinFilter(FilterBase): + def filter_js(self, js): - return jsmin(js) \ No newline at end of file + return jsmin(js) diff --git a/apps/compress/filters/jsmin/jsmin.py b/apps/compress/filters/jsmin/jsmin.py index 4f9d384f..60202e19 100644 --- a/apps/compress/filters/jsmin/jsmin.py +++ b/apps/compress/filters/jsmin/jsmin.py @@ -32,6 +32,7 @@ from StringIO import StringIO + def jsmin(js): ins = StringIO(js) outs = StringIO() @@ -41,26 +42,33 @@ def jsmin(js): str = str[1:] return str + def isAlphanum(c): """return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. """ - return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or - (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126)); + return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') \ + or (c >= '0' and c <= '9') or c == '_' or c == '$' or c == '\\' \ + or (c is not None and ord(c) > 126) + class UnterminatedComment(Exception): pass + class UnterminatedStringLiteral(Exception): pass + class UnterminatedRegularExpression(Exception): pass + class JavascriptMinify(object): def _outA(self): self.outstream.write(self.theA) + def _outB(self): self.outstream.write(self.theB) @@ -75,7 +83,7 @@ class JavascriptMinify(object): c = self.instream.read(1) if c >= ' ' or c == '\n': return c - if c == '': # EOF + if c == '': # EOF return '\000' if c == '\r': return '\n' @@ -135,7 +143,6 @@ class JavascriptMinify(object): self._outA() self.theA = self._get() - if action <= 3: self.theB = self._next() if self.theB == '/' and (self.theA == '(' or self.theA == ',' or @@ -159,7 +166,6 @@ class JavascriptMinify(object): self._outA() self.theB = self._next() - def _jsmin(self): """Copy the input to the output, deleting the characters which are insignificant to JavaScript. Comments will be removed. Tabs will be @@ -215,4 +221,4 @@ class JavascriptMinify(object): if __name__ == '__main__': import sys jsm = JavascriptMinify() - jsm.minify(sys.stdin, sys.stdout) \ No newline at end of file + jsm.minify(sys.stdin, sys.stdout) diff --git a/apps/compress/filters/yui/__init__.py b/apps/compress/filters/yui/__init__.py index 1e2e711f..47b2cfb0 100644 --- a/apps/compress/filters/yui/__init__.py +++ b/apps/compress/filters/yui/__init__.py @@ -8,6 +8,7 @@ BINARY = getattr(settings, 'COMPRESS_YUI_BINARY', 'java -jar yuicompressor.jar') CSS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_CSS_ARGUMENTS', '') JS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_JS_ARGUMENTS', '') + class YUICompressorFilter(FilterBase): def filter_common(self, content, type_, arguments): @@ -41,4 +42,4 @@ class YUICompressorFilter(FilterBase): return self.filter_common(js, 'js', JS_ARGUMENTS) def filter_css(self, css): - return self.filter_common(css, 'css', CSS_ARGUMENTS) \ No newline at end of file + return self.filter_common(css, 'css', CSS_ARGUMENTS) diff --git a/apps/compress/management/commands/synccompress.py b/apps/compress/management/commands/synccompress.py index 8217979d..b1cdafca 100644 --- a/apps/compress/management/commands/synccompress.py +++ b/apps/compress/management/commands/synccompress.py @@ -3,6 +3,7 @@ from optparse import make_option from django.conf import settings + class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--force', action='store_true', default=False, help='Force update of all files, even if the source files are older than the current compressed file.'), @@ -11,14 +12,14 @@ class Command(NoArgsCommand): args = '' def handle_noargs(self, **options): - + force = options.get('force', False) verbosity = int(options.get('verbosity', 1)) from compress.utils import needs_update, filter_css, filter_js for name, css in settings.COMPRESS_CSS.items(): - u, version = needs_update(css['output_filename'], + u, version = needs_update(css['output_filename'], css['source_filenames']) if (force or u) or verbosity >= 2: @@ -34,7 +35,7 @@ class Command(NoArgsCommand): print for name, js in settings.COMPRESS_JS.items(): - u, version = needs_update(js['output_filename'], + u, version = needs_update(js['output_filename'], js['source_filenames']) if (force or u) or verbosity >= 2: @@ -50,7 +51,7 @@ class Command(NoArgsCommand): print # Backwards compatibility for Django r9110 -if not [opt for opt in Command.option_list if opt.dest=='verbosity']: +if not [opt for opt in Command.option_list if opt.dest == 'verbosity']: Command.option_list += ( make_option('--verbosity', '-v', action="store", dest="verbosity", default='1', type='choice', choices=['0', '1', '2'], diff --git a/apps/compress/templatetags/compressed.py b/apps/compress/templatetags/compressed.py index f9c55f92..eeed319b 100644 --- a/apps/compress/templatetags/compressed.py +++ b/apps/compress/templatetags/compressed.py @@ -2,13 +2,12 @@ import os from django import template -from django.conf import settings as django_settings - from compress.conf import settings from compress.utils import media_root, media_url, needs_update, filter_css, filter_js, get_output_filename, get_version, get_version_from_file register = template.Library() + def render_common(template_name, obj, filename, version): if settings.COMPRESS: filename = get_output_filename(filename, version) @@ -19,16 +18,20 @@ def render_common(template_name, obj, filename, version): context['url'] = filename else: context['url'] = media_url(filename, prefix) - + return template.loader.render_to_string(template_name, context) + def render_css(css, filename, version=None): return render_common(css.get('template_name', 'compress/css.html'), css, filename, version) + def render_js(js, filename, version=None): return render_common(js.get('template_name', 'compress/js.html'), js, filename, version) + class CompressedCSSNode(template.Node): + def __init__(self, name): self.name = name @@ -38,14 +41,14 @@ class CompressedCSSNode(template.Node): try: css = settings.COMPRESS_CSS[css_name] except KeyError: - return '' # fail silently, do not return anything if an invalid group is specified + return '' # fail silently, do not return anything if an invalid group is specified if settings.COMPRESS: version = None if settings.COMPRESS_AUTO: - u, version = needs_update(css['output_filename'], + u, version = needs_update(css['output_filename'], css['source_filenames']) if u: filter_css(css) @@ -53,7 +56,7 @@ class CompressedCSSNode(template.Node): filename_base, filename = os.path.split(css['output_filename']) path_name = media_root(filename_base) version = get_version_from_file(path_name, filename) - + return render_css(css, css['output_filename'], version) else: # output source files @@ -63,7 +66,9 @@ class CompressedCSSNode(template.Node): return r + class CompressedJSNode(template.Node): + def __init__(self, name): self.name = name @@ -73,24 +78,24 @@ class CompressedJSNode(template.Node): try: js = settings.COMPRESS_JS[js_name] except KeyError: - return '' # fail silently, do not return anything if an invalid group is specified - + return '' # fail silently, do not return anything if an invalid group is specified + if 'external_urls' in js: r = '' for url in js['external_urls']: r += render_js(js, url) return r - + if settings.COMPRESS: version = None if settings.COMPRESS_AUTO: - u, version = needs_update(js['output_filename'], + u, version = needs_update(js['output_filename'], js['source_filenames']) if u: filter_js(js) - else: + else: filename_base, filename = os.path.split(js['output_filename']) path_name = media_root(filename_base) version = get_version_from_file(path_name, filename) @@ -103,6 +108,7 @@ class CompressedJSNode(template.Node): r += render_js(js, source_file) return r + #@register.tag def compressed_css(parser, token): try: @@ -113,6 +119,7 @@ def compressed_css(parser, token): return CompressedCSSNode(name) compressed_css = register.tag(compressed_css) + #@register.tag def compressed_js(parser, token): try: @@ -121,4 +128,5 @@ def compressed_js(parser, token): raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_JS setting' % token.split_contents()[0] return CompressedJSNode(name) + compressed_js = register.tag(compressed_js) diff --git a/apps/compress/utils.py b/apps/compress/utils.py index 5e13222f..1e5f4cc0 100644 --- a/apps/compress/utils.py +++ b/apps/compress/utils.py @@ -9,6 +9,7 @@ from django.dispatch import dispatcher from compress.conf import settings from compress.signals import css_filtered, js_filtered + def get_class(class_string): """ Convert a string version of a function name to the callable object. @@ -26,6 +27,7 @@ def get_class(class_string): return class_string + def get_mod_func(callback): """ Converts 'django.views.news.stories.story_detail' to @@ -36,7 +38,8 @@ def get_mod_func(callback): dot = callback.rindex('.') except ValueError: return callback, '' - return callback[:dot], callback[dot+1:] + return callback[:dot], callback[dot + 1:] + def needs_update(output_file, source_files, verbosity=0): """ @@ -44,27 +47,30 @@ def needs_update(output_file, source_files, verbosity=0): """ version = get_version(source_files) - + on = get_output_filename(output_file, version) compressed_file_full = media_root(on) if not os.path.exists(compressed_file_full): return True, version - + update_needed = getattr(get_class(settings.COMPRESS_VERSIONING)(), 'needs_update')(output_file, source_files, version) return update_needed + def media_root(filename): """ Return the full path to ``filename``. ``filename`` is a relative path name in MEDIA_ROOT """ return os.path.join(django_settings.STATIC_ROOT, filename) + def media_url(url, prefix=None): if prefix: return prefix + urlquote(url) return django_settings.STATIC_URL + urlquote(url) + def concat(filenames, separator=''): """ Concatenate the files from the list of the ``filenames``, ouput separated with ``separator``. @@ -77,9 +83,11 @@ def concat(filenames, separator=''): fd.close() return r + def max_mtime(files): return int(max([os.stat(media_root(f)).st_mtime for f in files])) + def save_file(filename, contents): dirname = os.path.dirname(media_root(filename)) if not os.path.exists(dirname): @@ -88,16 +96,19 @@ def save_file(filename, contents): fd.write(contents) fd.close() + def get_output_filename(filename, version): if settings.COMPRESS_VERSION and version is not None: return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, version) else: return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, settings.COMPRESS_VERSION_DEFAULT) + def get_version(source_files, verbosity=0): version = getattr(get_class(settings.COMPRESS_VERSIONING)(), 'get_version')(source_files) return version - + + def get_version_from_file(path, filename): regex = re.compile(r'^%s$' % (get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'([A-Za-z0-9]+)'))) for f in os.listdir(path): @@ -105,19 +116,21 @@ def get_version_from_file(path, filename): if result and result.groups(): return result.groups()[0] -def remove_files(path, filename, verbosity=0): + +def remove_files(path, filename, verbosity=0): regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'[A-Za-z0-9]+')))) if os.path.exists(path): for f in os.listdir(path): if regex.match(f): if verbosity >= 1: print "Removing outdated file %s" % f - + os.unlink(os.path.join(path, f)) + def filter_common(obj, verbosity, filters, attr, separator, signal): output = concat(obj['source_filenames'], separator) - + filename = get_output_filename(obj['output_filename'], get_version(obj['source_filenames'])) if settings.COMPRESS_VERSION: @@ -132,8 +145,10 @@ def filter_common(obj, verbosity, filters, attr, separator, signal): save_file(filename, output) signal.send(None) + def filter_css(css, verbosity=0): return filter_common(css, verbosity, filters=settings.COMPRESS_CSS_FILTERS, attr='filter_css', separator='', signal=css_filtered) + def filter_js(js, verbosity=0): return filter_common(js, verbosity, filters=settings.COMPRESS_JS_FILTERS, attr='filter_js', separator='', signal=js_filtered) diff --git a/apps/compress/versioning/base.py b/apps/compress/versioning/base.py index 8b815196..0be9acab 100644 --- a/apps/compress/versioning/base.py +++ b/apps/compress/versioning/base.py @@ -2,12 +2,13 @@ class VersioningBase(object): def get_version(self, source_files): raise NotImplementedError - + def needs_update(self, output_file, source_files, version): raise NotImplementedError - + + class VersioningError(Exception): """ This exception is raised when version creation fails """ - pass \ No newline at end of file + pass diff --git a/apps/compress/versioning/hash/__init__.py b/apps/compress/versioning/hash/__init__.py index 74721fe0..a298961c 100644 --- a/apps/compress/versioning/hash/__init__.py +++ b/apps/compress/versioning/hash/__init__.py @@ -6,30 +6,32 @@ from compress.conf import settings from compress.utils import concat, get_output_filename from compress.versioning.base import VersioningBase + class HashVersioningBase(VersioningBase): + def __init__(self, hash_method): self.hash_method = hash_method - + def needs_update(self, output_file, source_files, version): output_file_name = get_output_filename(output_file, version) ph = settings.COMPRESS_VERSION_PLACEHOLDER of = output_file try: phi = of.index(ph) - old_version = output_file_name[phi:phi+len(ph)-len(of)] + old_version = output_file_name[phi:phi + len(ph) - len(of)] return (version != old_version), version except ValueError: # no placeholder found, do not update, manual update if needed return False, version - + def get_version(self, source_files): buf = concat(source_files) s = cStringIO.StringIO(buf) version = self.get_hash(s) s.close() - return version - - def get_hash(self, f, CHUNK=2**16): + return version + + def get_hash(self, f, CHUNK=2 ** 16): m = self.hash_method() while 1: chunk = f.read(CHUNK) @@ -38,10 +40,14 @@ class HashVersioningBase(VersioningBase): m.update(chunk) return m.hexdigest() + class MD5Versioning(HashVersioningBase): + def __init__(self): super(MD5Versioning, self).__init__(md5) + class SHA1Versioning(HashVersioningBase): + def __init__(self): - super(SHA1Versioning, self).__init__(sha1) \ No newline at end of file + super(SHA1Versioning, self).__init__(sha1) diff --git a/apps/compress/versioning/mtime/__init__.py b/apps/compress/versioning/mtime/__init__.py index 31b9d0b9..010aba9a 100644 --- a/apps/compress/versioning/mtime/__init__.py +++ b/apps/compress/versioning/mtime/__init__.py @@ -3,6 +3,7 @@ import os from compress.utils import get_output_filename, media_root from compress.versioning.base import VersioningBase + class MTimeVersioning(VersioningBase): def get_version(self, source_files): @@ -16,4 +17,3 @@ class MTimeVersioning(VersioningBase): compressed_file_full = media_root(output_file_name) return (int(os.stat(compressed_file_full).st_mtime) < int(version)), version - diff --git a/apps/django_cas/backends.py b/apps/django_cas/backends.py index f14619d0..328469a9 100644 --- a/apps/django_cas/backends.py +++ b/apps/django_cas/backends.py @@ -7,6 +7,7 @@ from django_cas.models import User __all__ = ['CASBackend'] + def _verify_cas1(ticket, service): """Verifies CAS 1.0 authentication ticket. @@ -77,7 +78,7 @@ class CASBackend(object): if not username: return None try: - user = User.objects.get(username__iexact = username) + user = User.objects.get(username__iexact=username) except User.DoesNotExist: # user will have an "unusable" password user = User.objects.create_user(username, '') @@ -88,6 +89,6 @@ class CASBackend(object): """Retrieve the user's entry in the User model if it exists""" try: - return User.objects.get(pk = user_id) + return User.objects.get(pk=user_id) except User.DoesNotExist: return None diff --git a/apps/django_cas/decorators.py b/apps/django_cas/decorators.py index 54a3734f..0bf989b9 100644 --- a/apps/django_cas/decorators.py +++ b/apps/django_cas/decorators.py @@ -12,6 +12,7 @@ from django.utils.http import urlquote __all__ = ['login_required', 'permission_required', 'user_passes_test'] + def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): """Replacement for django.contrib.auth.decorators.user_passes_test that diff --git a/apps/django_cas/middleware.py b/apps/django_cas/middleware.py index 8d5243d9..35fd0d5d 100644 --- a/apps/django_cas/middleware.py +++ b/apps/django_cas/middleware.py @@ -12,6 +12,7 @@ from django_cas.views import login as cas_login, logout as cas_logout __all__ = ['CASMiddleware'] + class CASMiddleware(object): """Middleware that allows CAS authentication on admin pages""" diff --git a/apps/django_cas/models.py b/apps/django_cas/models.py index 7658df9a..85206e00 100644 --- a/apps/django_cas/models.py +++ b/apps/django_cas/models.py @@ -1,2 +1,2 @@ from django.db import models -from django.contrib.auth.models import User \ No newline at end of file +from django.contrib.auth.models import User diff --git a/apps/django_cas/views.py b/apps/django_cas/views.py index 8bbe566b..dee32797 100644 --- a/apps/django_cas/views.py +++ b/apps/django_cas/views.py @@ -9,7 +9,8 @@ from django.contrib.auth import REDIRECT_FIELD_NAME __all__ = ['login', 'logout'] -def _service_url(request, redirect_to = None): + +def _service_url(request, redirect_to=None): """Generates application service URL for CAS""" protocol = ('http://', 'https://')[request.is_secure()] @@ -51,7 +52,7 @@ def _login_url(service): return urljoin(settings.CAS_SERVER_URL, 'login') + '?' + urlencode(params) -def _logout_url(request, next_page = None): +def _logout_url(request, next_page=None): """Generates CAS logout URL""" url = urljoin(settings.CAS_SERVER_URL, 'logout') @@ -62,7 +63,7 @@ def _logout_url(request, next_page = None): return url -def login(request, next_page = None, required = False): +def login(request, next_page=None, required=False): """Forwards to CAS login URL or verifies CAS ticket""" print "LOGIN original NEXT_PAGE:", next_page @@ -73,7 +74,7 @@ def login(request, next_page = None, required = False): if request.user.is_authenticated(): message = "You are logged in as %s." % request.user.username - request.user.message_set.create(message = message) + request.user.message_set.create(message=message) return HttpResponseRedirect(next_page) ticket = request.GET.get('ticket') service = _service_url(request, next_page) @@ -81,12 +82,12 @@ def login(request, next_page = None, required = False): print "SERVICE: ", service if ticket: from django.contrib import auth - user = auth.authenticate(ticket = ticket, service = service) + user = auth.authenticate(ticket=ticket, service=service) if user is not None: auth.login(request, user) name = user.first_name or user.username message = "Login succeeded. Welcome, %s." % name - user.message_set.create(message = message) + user.message_set.create(message=message) return HttpResponseRedirect(next_page) elif settings.CAS_RETRY_LOGIN or required: return HttpResponseRedirect(_login_url(service)) @@ -98,7 +99,7 @@ def login(request, next_page = None, required = False): return HttpResponseRedirect(_login_url(service)) -def logout(request, next_page = None): +def logout(request, next_page=None): """Redirects to CAS logout page""" from django.contrib.auth import logout diff --git a/apps/filebrowser/base.py b/apps/filebrowser/base.py index 4f0a024e..c1240263 100644 --- a/apps/filebrowser/base.py +++ b/apps/filebrowser/base.py @@ -1,16 +1,22 @@ -# coding: utf-8 +# -*- coding: utf-8 -import locale - -import os, re, datetime -from time import gmtime, strftime from django.conf import settings from django.utils.encoding import smart_unicode +from filebrowser.fb_settings import * + +from filebrowser.functions import _get_file_type, _url_join, _is_selectable, \ + _get_version_path + +from time import gmtime, strftime + +import locale +import os +import re +import datetime import urllib + # filebrowser imports -from filebrowser.fb_settings import * -from filebrowser.functions import _get_file_type, _url_join, _is_selectable, _get_version_path # PIL import if STRICT_PIL: @@ -27,10 +33,11 @@ def filesystem_encoding(ucode): ucode = urllib.quote(ucode) return ucode + class FileObject(object): """ The FileObject represents a File on the Server. - + PATH has to be relative to MEDIA_ROOT. """ @@ -38,7 +45,7 @@ class FileObject(object): self.path = path self.head = os.path.split(path)[0] self.filename = os.path.split(path)[1] - self.filename_lower = self.filename.lower() # important for sorting + self.filename_lower = self.filename.lower() # important for sorting self.filetype = _get_file_type(self.filename) def _filesize(self): @@ -205,6 +212,3 @@ class FileObject(object): def __unicode__(self): return u"%s" % self.url_save - - - diff --git a/apps/filebrowser/decorators.py b/apps/filebrowser/decorators.py index 070fa2d0..52978898 100644 --- a/apps/filebrowser/decorators.py +++ b/apps/filebrowser/decorators.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# -*- coding: utf-8 from django.contrib.sessions.models import Session from django.shortcuts import get_object_or_404, render_to_response @@ -12,7 +12,7 @@ def flash_login_required(function): Decorator to recognize a user by its session. Used for Flash-Uploading. """ - + def decorator(request, *args, **kwargs): try: engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) @@ -25,5 +25,3 @@ def flash_login_required(function): request.user = get_object_or_404(User, pk=user_id) return function(request, *args, **kwargs) return decorator - - diff --git a/apps/filebrowser/fb_settings.py b/apps/filebrowser/fb_settings.py index 59de8256..57bec8eb 100644 --- a/apps/filebrowser/fb_settings.py +++ b/apps/filebrowser/fb_settings.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# -*- coding: utf-8 import os from django.conf import settings @@ -42,7 +42,7 @@ EXTENSIONS = getattr(settings, "FILEBROWSER_EXTENSIONS", { 'Video': ['.mov', '.wmv', '.mpeg', '.mpg', '.avi', '.rm'], 'Document': ['.pdf', '.doc', '.rtf', '.txt', '.xls', '.csv'], 'Sound': ['.mp3', '.mp4', '.wav', '.aiff', '.midi', '.m4p'], - 'Code': ['.html', '.py', '.js', '.css'] + 'Code': ['.html', '.py', '.js', '.css'], }) # Define different formats for allowed selections. @@ -107,5 +107,3 @@ _('Video') _('Document') _('Sound') _('Code') - - diff --git a/apps/filebrowser/fields.py b/apps/filebrowser/fields.py index 85e981b9..78419cdc 100644 --- a/apps/filebrowser/fields.py +++ b/apps/filebrowser/fields.py @@ -1,4 +1,4 @@ -# coding: utf-8 +# -*- coding: utf-8 import os @@ -16,12 +16,13 @@ from filebrowser.functions import _url_to_path, _dir_from_url, _get_version_path from filebrowser.fb_settings import * from filebrowser.base import FileObject + class FileBrowseWidget(Input): input_type = 'text' - + class Media: - js = (os.path.join(URL_FILEBROWSER_MEDIA, 'js/AddFileBrowser.js'), ) - + js = (os.path.join(URL_FILEBROWSER_MEDIA, 'js/AddFileBrowser.js'),) + def __init__(self, attrs=None): self.directory = attrs.get('directory', '') self.extensions = attrs.get('extensions', '') @@ -30,7 +31,7 @@ class FileBrowseWidget(Input): self.attrs = attrs.copy() else: self.attrs = {} - + def render(self, name, value, attrs=None): if value is None: value = "" @@ -47,15 +48,15 @@ class FileBrowseWidget(Input): except: pass return render_to_string("filebrowser/custom_field.html", locals()) - + class FileBrowseFormField(forms.CharField): widget = FileBrowseWidget - + default_error_messages = { 'extension': _(u'Extension %(ext)s is not allowed. Only %(allowed)s is allowed.'), } - + def __init__(self, max_length=None, min_length=None, directory=None, extensions=None, format=None, *args, **kwargs): @@ -66,7 +67,7 @@ class FileBrowseFormField(forms.CharField): self.format = format or '' self.extensions = extensions or EXTENSIONS.get(format) super(FileBrowseFormField, self).__init__(*args, **kwargs) - + def clean(self, value): value = super(FileBrowseFormField, self).clean(value) if value == '': @@ -75,34 +76,33 @@ class FileBrowseFormField(forms.CharField): if self.extensions and not file_extension in self.extensions: raise forms.ValidationError(self.error_messages['extension'] % {'ext': file_extension, 'allowed': ", ".join(self.extensions)}) return value - + class FileBrowseField(Field): __metaclass__ = models.SubfieldBase - + def __init__(self, *args, **kwargs): self.directory = kwargs.pop('directory', '') self.extensions = kwargs.pop('extensions', '') self.format = kwargs.pop('format', '') return super(FileBrowseField, self).__init__(*args, **kwargs) - + def to_python(self, value): if not value or isinstance(value, FileObject): return value return FileObject(_url_to_path(value)) - + def get_db_prep_value(self, value): if value is None: return None return unicode(value) - - + def get_manipulator_field_objs(self): return [oldforms.TextField] - + def get_internal_type(self): return "CharField" - + def formfield(self, **kwargs): attrs = {} attrs["directory"] = self.directory @@ -113,9 +113,7 @@ class FileBrowseField(Field): 'widget': FileBrowseWidget(attrs=attrs), 'directory': self.directory, 'extensions': self.extensions, - 'format': self.format + 'format': self.format, } defaults.update(kwargs) return super(FileBrowseField, self).formfield(**defaults) - - diff --git a/apps/filebrowser/forms.py b/apps/filebrowser/forms.py index eef418cc..465c2bd6 100644 --- a/apps/filebrowser/forms.py +++ b/apps/filebrowser/forms.py @@ -1,6 +1,7 @@ # coding: utf-8 -import re, os +import os +import re from django import forms from django.forms.formsets import BaseFormSet @@ -12,18 +13,19 @@ from filebrowser.functions import _get_file_type, _convert_filename alnum_name_re = re.compile(r'^[\sa-zA-Z0-9._/-]+$') + class MakeDirForm(forms.Form): """ Form for creating Directory. """ - + def __init__(self, path, *args, **kwargs): self.path = path super(MakeDirForm, self).__init__(*args, **kwargs) - - dir_name = forms.CharField(widget=forms.TextInput(attrs=dict({ 'class': 'vTextField' }, max_length=50, min_length=3)), label=_(u'Name'), help_text=_(u'Only letters, numbers, underscores, spaces and hyphens are allowed.'), required=True) - - def clean_dir_name(self): + + dir_name = forms.CharField(widget=forms.TextInput(attrs=dict({'class': 'vTextField'}, max_length=50, min_length=3)), label=_(u'Name'), help_text=_(u'Only letters, numbers, underscores, spaces and hyphens are allowed.'), required=True) + + def clean_dir_name(self): if self.cleaned_data['dir_name']: # only letters, numbers, underscores, spaces and hyphens are allowed. if not alnum_name_re.search(self.cleaned_data['dir_name']): @@ -32,20 +34,20 @@ class MakeDirForm(forms.Form): if os.path.isdir(os.path.join(self.path, _convert_filename(self.cleaned_data['dir_name']))): raise forms.ValidationError(_(u'The Folder already exists.')) return _convert_filename(self.cleaned_data['dir_name']) - + class RenameForm(forms.Form): """ Form for renaming File/Directory. """ - + def __init__(self, path, file_extension, *args, **kwargs): self.path = path self.file_extension = file_extension super(RenameForm, self).__init__(*args, **kwargs) - - name = forms.CharField(widget=forms.TextInput(attrs=dict({ 'class': 'vTextField' }, max_length=50, min_length=3)), label=_(u'New Name'), help_text=_('Only letters, numbers, underscores, spaces and hyphens are allowed.'), required=True) - + + name = forms.CharField(widget=forms.TextInput(attrs=dict({'class': 'vTextField'}, max_length=50, min_length=3)), label=_(u'New Name'), help_text=_('Only letters, numbers, underscores, spaces and hyphens are allowed.'), required=True) + def clean_name(self): if self.cleaned_data['name']: # only letters, numbers, underscores, spaces and hyphens are allowed. @@ -55,5 +57,3 @@ class RenameForm(forms.Form): if os.path.isdir(os.path.join(self.path, _convert_filename(self.cleaned_data['name']))) or os.path.isfile(os.path.join(self.path, _convert_filename(self.cleaned_data['name']) + self.file_extension)): raise forms.ValidationError(_(u'The File/Folder already exists.')) return _convert_filename(self.cleaned_data['name']) - - diff --git a/apps/filebrowser/functions.py b/apps/filebrowser/functions.py index afcdffd8..c8cde3f6 100644 --- a/apps/filebrowser/functions.py +++ b/apps/filebrowser/functions.py @@ -1,11 +1,15 @@ -# coding: utf-8 +# -*- coding: utf-8 + +import os +import re +import decimal from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe from time import gmtime, strftime, localtime, mktime, time from django.core.files import File from django.core.files.storage import default_storage -import os, re, decimal + from urlparse import urlparse # filebrowser imports @@ -25,7 +29,7 @@ def _url_to_path(value): """ Change URL to PATH. Value has to be an URL relative to MEDIA URL or a full URL (including MEDIA_URL). - + Returns a PATH relative to MEDIA_ROOT. """ print "URL2PATH", repr(value) @@ -38,7 +42,7 @@ def _path_to_url(value): """ Change PATH to URL. Value has to be a PATH relative to MEDIA_ROOT. - + Return an URL relative to MEDIA_ROOT. """ mediaroot_re = re.compile(r'^(%s)' % (MEDIA_ROOT)) @@ -64,7 +68,7 @@ def _get_version_path(value, version_prefix): """ Construct the PATH to an Image version. Value has to be server-path, relative to MEDIA_ROOT. - + version_filename = filename + version_prefix + ext Returns a path relative to MEDIA_ROOT. """ @@ -135,16 +139,23 @@ def _get_filterdate(filterDate, dateTime): Get filterdate. """ - returnvalue = '' dateYear = strftime("%Y", gmtime(dateTime)) dateMonth = strftime("%m", gmtime(dateTime)) dateDay = strftime("%d", gmtime(dateTime)) - if filterDate == 'today' and int(dateYear) == int(localtime()[0]) and int(dateMonth) == int(localtime()[1]) and int(dateDay) == int(localtime()[2]): returnvalue = 'true' - elif filterDate == 'thismonth' and dateTime >= time() - 2592000: returnvalue = 'true' - elif filterDate == 'thisyear' and int(dateYear) == int(localtime()[0]): returnvalue = 'true' - elif filterDate == 'past7days' and dateTime >= time() - 604800: returnvalue = 'true' - elif filterDate == '': returnvalue = 'true' - return returnvalue + + if filterDate == 'today' and int(dateYear) == int(localtime()[0]) and int(dateMonth) == int(localtime()[1]) and int(dateDay) == int(localtime()[2]): + return 'true' + + if filterDate == 'thismonth' and dateTime >= time() - 2592000: + return 'true' + + if filterDate == 'thisyear' and int(dateYear) == int(localtime()[0]): + return 'true' + + if filterDate == 'past7days' and dateTime >= time() - 604800: + return 'true' + + return 'true' if filterDate == '' else '' def _get_settings_var(): @@ -214,7 +225,7 @@ def _is_selectable(filename, selecttype): return select_types -def _version_generator(value, version_prefix, force = None): +def _version_generator(value, version_prefix, force=None): """ Generate Version for an Image. value has to be a serverpath relative to MEDIA_ROOT. @@ -229,7 +240,7 @@ def _version_generator(value, version_prefix, force = None): from PIL import ImageFile except ImportError: import ImageFile - ImageFile.MAXBLOCK = IMAGE_MAXBLOCK # default is 64k + ImageFile.MAXBLOCK = IMAGE_MAXBLOCK # default is 64k try: im = Image.open(os.path.join(MEDIA_ROOT, value)) @@ -241,9 +252,9 @@ def _version_generator(value, version_prefix, force = None): os.chmod(version_dir, 0775) version = scale_and_crop(im, VERSIONS[version_prefix]['width'], VERSIONS[version_prefix]['height'], VERSIONS[version_prefix]['opts']) try: - version.save(absolute_version_path, quality = 90, optimize = 1) + version.save(absolute_version_path, quality=90, optimize=1) except IOError: - version.save(absolute_version_path, quality = 90) + version.save(absolute_version_path, quality=90) return version_path except: return None @@ -266,7 +277,7 @@ def scale_and_crop(im, width, height, opts): r = min(xr / x, yr / y) if r < 1.0 or (r > 1.0 and 'upscale' in opts): - im = im.resize((int(x * r), int(y * r)), resample = Image.ANTIALIAS) + im = im.resize((int(x * r), int(y * r)), resample=Image.ANTIALIAS) if 'crop' in opts: x, y = [float(v) for v in im.size] @@ -282,6 +293,3 @@ def _convert_filename(value): return value.replace(" ", "_").lower() else: return value - - - diff --git a/apps/filebrowser/models.py b/apps/filebrowser/models.py index a11084e2..655d082b 100644 --- a/apps/filebrowser/models.py +++ b/apps/filebrowser/models.py @@ -1 +1 @@ -# This file is only necessary for the tests to work \ No newline at end of file +# This file is only necessary for the tests to work diff --git a/apps/filebrowser/templatetags/fb_tags.py b/apps/filebrowser/templatetags/fb_tags.py index 3733485f..21aed146 100644 --- a/apps/filebrowser/templatetags/fb_tags.py +++ b/apps/filebrowser/templatetags/fb_tags.py @@ -8,13 +8,14 @@ from filebrowser.fb_settings import SELECT_FORMATS register = template.Library() -@register.inclusion_tag('filebrowser/include/_response.html', takes_context = True) -def query_string(context, add = None, remove = None): + +@register.inclusion_tag('filebrowser/include/_response.html', takes_context=True) +def query_string(context, add=None, remove=None): """ Allows the addition and removal of query string parameters. - + _response.html is just {{ response }} - + Usage: http://www.url.com/{% query_string "param_to_add=value, param_to_add=value" "param_to_remove, params_to_remove" %} http://www.url.com/{% query_string "" "filter" %}filter={{new_filter}} @@ -25,10 +26,10 @@ def query_string(context, add = None, remove = None): remove = string_to_list(remove) params = context['query'].copy() response = get_query_string(params, add, remove) - return {'response': smart_unicode(response) } + return {'response': smart_unicode(response)} -def query_helper(query, add = None, remove = None): +def query_helper(query, add=None, remove=None): """ Helper Function for use within views. """ @@ -38,12 +39,16 @@ def query_helper(query, add = None, remove = None): return get_query_string(params, add, remove) -def get_query_string(p, new_params = None, remove = None): +def get_query_string(p, new_params=None, remove=None): """ Add and remove query parameters. From `django.contrib.admin`. """ - if new_params is None: new_params = {} - if remove is None: remove = [] + if new_params is None: + new_params = {} + + if remove is None: + remove = [] + for r in remove: for k in p.keys(): if k.startswith(r): @@ -59,29 +64,26 @@ def get_query_string(p, new_params = None, remove = None): def string_to_dict(string): """ Usage:: - + {{ url|thumbnail:"width=10,height=20" }} {{ url|thumbnail:"width=10" }} {{ url|thumbnail:"height=20" }} """ kwargs = {} + if string: string = str(string) - if ',' not in string: - # ensure at least one ',' - string += ',' - for arg in string.split(','): - arg = arg.strip() - if arg == '': continue - kw, val = arg.split('=', 1) - kwargs[kw] = val + + args = (arg.strip() for arg in string.split(',') if not arg.iswhitespace()) + kwargs.update(arg.split('=', 1) for arg in args) + return kwargs def string_to_list(string): """ Usage:: - + {{ url|thumbnail:"width,height" }} """ args = [] @@ -92,7 +94,8 @@ def string_to_list(string): string += ',' for arg in string.split(','): arg = arg.strip() - if arg == '': continue + if arg == '': + continue args.append(arg) return args @@ -126,7 +129,7 @@ def selectable(parser, token): try: tag, filetype, format = token.split_contents() except: - raise TemplateSyntaxError, "%s tag requires 2 arguments" % token.contents.split()[0] + raise TemplateSyntaxError("%s tag requires 2 arguments" % token.contents.split()[0]) return SelectableNode(filetype, format) diff --git a/apps/filebrowser/templatetags/fb_versions.py b/apps/filebrowser/templatetags/fb_versions.py index cdb05194..cb67b2fd 100644 --- a/apps/filebrowser/templatetags/fb_versions.py +++ b/apps/filebrowser/templatetags/fb_versions.py @@ -1,19 +1,22 @@ # coding: utf-8 -import os, re -from time import gmtime +import os +import re from django.template import Library, Node, Variable, VariableDoesNotExist, TemplateSyntaxError from django.conf import settings from django.utils.encoding import force_unicode -import urllib # filebrowser imports from filebrowser.fb_settings import MEDIA_ROOT, MEDIA_URL, VERSIONS from filebrowser.functions import _url_to_path, _path_to_url, _get_version_path, _version_generator from filebrowser.base import FileObject +import logging +logger = logging.getLogger("django.filebrowser") + register = Library() + class VersionNode(Node): def __init__(self, src, version_prefix): self.src = Variable(src) @@ -44,37 +47,35 @@ class VersionNode(Node): version_path = _version_generator(_url_to_path(str(source)), version_prefix) elif os.path.getmtime(os.path.join(MEDIA_ROOT, _url_to_path(str(source)))) > os.path.getmtime(os.path.join(MEDIA_ROOT, version_path)): # recreate version if original image was updated - version_path = _version_generator(_url_to_path(str(source)), version_prefix, force = True) + version_path = _version_generator(_url_to_path(str(source)), version_prefix, force=True) return _path_to_url(version_path) - except Exception, e: - import traceback, sys - print "FB VERSION ERROR" - traceback.print_exc(file = sys.stdout) + except Exception: + logger.exception("Version error") return u"" - def version(parser, token): """ Displaying a version of an existing Image according to the predefined VERSIONS settings (see fb_settings). {% version field_name version_prefix %} - + Use {% version my_image 'medium' %} in order to display the medium-size version of an Image stored in a field name my_image. - + version_prefix can be a string or a variable. if version_prefix is a string, use quotes. """ try: tag, src, version_prefix = token.split_contents() except: - raise TemplateSyntaxError, "%s tag requires 2 arguments" % token.contents.split()[0] + raise TemplateSyntaxError("%s tag requires 2 arguments" % token.contents.split()[0]) if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")) and version_prefix.lower()[1:-1] not in VERSIONS: - raise TemplateSyntaxError, "%s tag received bad version_prefix %s" % (tag, version_prefix) + raise TemplateSyntaxError("%s tag received bad version_prefix %s" % (tag, version_prefix)) return VersionNode(src, version_prefix) class VersionObjectNode(Node): + def __init__(self, src, version_prefix, var_name): self.var_name = var_name self.src = Variable(src) @@ -103,7 +104,7 @@ class VersionObjectNode(Node): version_path = _version_generator(_url_to_path(str(source)), version_prefix) elif os.path.getmtime(os.path.join(MEDIA_ROOT, _url_to_path(str(source)))) > os.path.getmtime(os.path.join(MEDIA_ROOT, version_path)): # recreate version if original image was updated - version_path = _version_generator(_url_to_path(str(source)), version_prefix, force = True) + version_path = _version_generator(_url_to_path(str(source)), version_prefix, force=True) context[self.var_name] = FileObject(version_path) except: context[self.var_name] = "" @@ -114,12 +115,12 @@ def version_object(parser, token): """ Returns a context variable 'version_object'. {% version_object field_name version_prefix %} - + Use {% version_object my_image 'medium' %} in order to retrieve the medium version of an Image stored in a field name my_image. Use {% version_object my_image 'medium' as var %} in order to use 'var' as your context variable. - + version_prefix can be a string or a variable. if version_prefix is a string, use quotes. """ @@ -127,17 +128,18 @@ def version_object(parser, token): #tag, src, version_prefix = token.split_contents() tag, arg = token.contents.split(None, 1) except: - raise TemplateSyntaxError, "%s tag requires arguments" % token.contents.split()[0] + raise TemplateSyntaxError("%s tag requires arguments" % token.contents.split()[0]) m = re.search(r'(.*?) (.*?) as (\w+)', arg) if not m: - raise TemplateSyntaxError, "%r tag had invalid arguments" % tag + raise TemplateSyntaxError("%r tag had invalid arguments" % tag) src, version_prefix, var_name = m.groups() if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")) and version_prefix.lower()[1:-1] not in VERSIONS: - raise TemplateSyntaxError, "%s tag received bad version_prefix %s" % (tag, version_prefix) + raise TemplateSyntaxError("%s tag received bad version_prefix %s" % (tag, version_prefix)) return VersionObjectNode(src, version_prefix, var_name) class VersionSettingNode(Node): + def __init__(self, version_prefix): if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")): self.version_prefix = version_prefix[1:-1] @@ -165,14 +167,12 @@ def version_setting(parser, token): try: tag, version_prefix = token.split_contents() except: - raise TemplateSyntaxError, "%s tag requires 1 argument" % token.contents.split()[0] + raise TemplateSyntaxError("%s tag requires 1 argument" % token.contents.split()[0]) if (version_prefix[0] == version_prefix[-1] and version_prefix[0] in ('"', "'")) and version_prefix.lower()[1:-1] not in VERSIONS: - raise TemplateSyntaxError, "%s tag received bad version_prefix %s" % (tag, version_prefix) + raise TemplateSyntaxError("%s tag received bad version_prefix %s" % (tag, version_prefix)) return VersionSettingNode(version_prefix) register.tag(version) register.tag(version_object) register.tag(version_setting) - - diff --git a/apps/filebrowser/urls.py b/apps/filebrowser/urls.py index 5cfc2596..60bfff8c 100644 --- a/apps/filebrowser/urls.py +++ b/apps/filebrowser/urls.py @@ -1,7 +1,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', - + # filebrowser urls url(r'^browse/$', 'filebrowser.views.browse', name="fb_browse"), url(r'^mkdir/', 'filebrowser.views.mkdir', name="fb_mkdir"), @@ -9,7 +9,7 @@ urlpatterns = patterns('', url(r'^rename/$', 'filebrowser.views.rename', name="fb_rename"), url(r'^delete/$', 'filebrowser.views.delete', name="fb_delete"), url(r'^versions/$', 'filebrowser.views.versions', name="fb_versions"), - + url(r'^check_file/$', 'filebrowser.views._check_file', name="fb_check"), url(r'^upload_file/$', 'filebrowser.views._upload_file', name="fb_do_upload"), diff --git a/apps/filebrowser/views.py b/apps/filebrowser/views.py index 30869a4d..7870b9d5 100644 --- a/apps/filebrowser/views.py +++ b/apps/filebrowser/views.py @@ -1,8 +1,7 @@ # coding: utf-8 # general imports -import os, re, locale -from time import gmtime, strftime +import re # django imports from django.shortcuts import render_to_response, HttpResponse @@ -21,15 +20,21 @@ from django.utils.encoding import smart_unicode, smart_str # filebrowser imports from filebrowser.fb_settings import * -from filebrowser.functions import _url_to_path, _path_to_url, _get_path, _get_file, _get_version_path, _get_breadcrumbs, _get_filterdate, _get_settings_var, _handle_file_upload, _get_file_type, _url_join, _convert_filename + +from filebrowser.functions import ( + _url_to_path, _path_to_url, _get_path, _get_file, _get_version_path, + _get_breadcrumbs, _get_filterdate, _get_settings_var, _handle_file_upload, + _get_file_type, _url_join, _convert_filename) + from filebrowser.templatetags.fb_tags import query_helper from filebrowser.base import FileObject from filebrowser.decorators import flash_login_required # Precompile regular expressions filter_re = [] + for exp in EXCLUDE: - filter_re.append(re.compile(exp)) + filter_re.append(re.compile(exp)) for k, v in VERSIONS.iteritems(): exp = (r'_%s.(%s)') % (k, '|'.join(EXTENSION_LIST)) filter_re.append(re.compile(exp)) @@ -47,16 +52,16 @@ def browse(request): if path is None: msg = _('Directory/File does not exist.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) if directory is None: # The DIRECTORY does not exist, raise an error to prevent eternal redirecting. - raise ImproperlyConfigured, _("Error finding upload directory. Maybe it does not exist?") + raise ImproperlyConfigured(_("Error finding upload directory. Maybe it does not exist?")) redirect_url = reverse("fb_browse") + query_helper(query, "", "dir") return HttpResponseRedirect(redirect_url) abs_path = os.path.join(MEDIA_ROOT, DIRECTORY, path) # INITIAL VARIABLES - results_var = {'results_total': 0, 'results_current': 0, 'delete_total': 0, 'images_total': 0, 'select_total': 0 } + results_var = {'results_total': 0, 'results_current': 0, 'delete_total': 0, 'images_total': 0, 'select_total': 0} counter = {} for k, v in EXTENSIONS.iteritems(): counter[k] = 0 @@ -106,7 +111,7 @@ def browse(request): counter[fileobject.filetype] += 1 # SORTING - files.sort(key = lambda e: getattr(e, request.GET.get('o', DEFAULT_ORDER))) + files.sort(key=lambda e: getattr(e, request.GET.get('o', DEFAULT_ORDER))) if request.GET.get('ot') == "desc": files.reverse() @@ -119,13 +124,14 @@ def browse(request): 'title': _(u'FileBrowser'), 'settings_var': _get_settings_var(), 'breadcrumbs': _get_breadcrumbs(query, path, ''), - }, context_instance = Context(request)) + }, context_instance=Context(request)) browse = staff_member_required(never_cache(browse)) # mkdir signals -filebrowser_pre_createdir = Signal(providing_args = ["path", "dirname"]) -filebrowser_post_createdir = Signal(providing_args = ["path", "dirname"]) +filebrowser_pre_createdir = Signal(providing_args=["path", "dirname"]) +filebrowser_post_createdir = Signal(providing_args=["path", "dirname"]) + def mkdir(request): """ @@ -139,7 +145,7 @@ def mkdir(request): path = _get_path(query.get('dir', '')) if path is None: msg = _('Directory/File does not exist.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect(reverse("fb_browse")) abs_path = os.path.join(MEDIA_ROOT, DIRECTORY, path) @@ -149,15 +155,15 @@ def mkdir(request): server_path = os.path.join(abs_path, form.cleaned_data['dir_name']) try: # PRE CREATE SIGNAL - filebrowser_pre_createdir.send(sender = request, path = path, dirname = form.cleaned_data['dir_name']) + filebrowser_pre_createdir.send(sender=request, path=path, dirname=form.cleaned_data['dir_name']) # CREATE FOLDER os.mkdir(server_path) os.chmod(server_path, 0775) # POST CREATE SIGNAL - filebrowser_post_createdir.send(sender = request, path = path, dirname = form.cleaned_data['dir_name']) + filebrowser_post_createdir.send(sender=request, path=path, dirname=form.cleaned_data['dir_name']) # MESSAGE & REDIRECT msg = _('The Folder %s was successfully created.') % (form.cleaned_data['dir_name']) - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) # on redirect, sort by date desc to see the new directory on top of the list # remove filter in order to actually _see_ the new folder redirect_url = reverse("fb_browse") + query_helper(query, "ot=desc,o=date", "ot,o,filter_type,filter_date,q") @@ -176,7 +182,7 @@ def mkdir(request): 'title': _(u'New Folder'), 'settings_var': _get_settings_var(), 'breadcrumbs': _get_breadcrumbs(query, path, _(u'New Folder')), - }, context_instance = Context(request)) + }, context_instance=Context(request)) mkdir = staff_member_required(never_cache(mkdir)) @@ -192,7 +198,7 @@ def upload(request): path = _get_path(query.get('dir', '')) if path is None: msg = _('Directory/File does not exist.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect(reverse("fb_browse")) abs_path = os.path.join(MEDIA_ROOT, DIRECTORY, path) @@ -207,7 +213,7 @@ def upload(request): 'settings_var': _get_settings_var(), 'breadcrumbs': _get_breadcrumbs(query, path, _(u'Upload')), 'session_key': session_key, - }, context_instance = Context(request)) + }, context_instance=Context(request)) upload = staff_member_required(never_cache(upload)) @@ -234,8 +240,9 @@ def _check_file(request): # upload signals -filebrowser_pre_upload = Signal(providing_args = ["path", "file"]) -filebrowser_post_upload = Signal(providing_args = ["path", "file"]) +filebrowser_pre_upload = Signal(providing_args=["path", "file"]) +filebrowser_post_upload = Signal(providing_args=["path", "file"]) + def _upload_file(request): """ @@ -253,7 +260,7 @@ def _upload_file(request): filedata = request.FILES['Filedata'] filedata.name = _convert_filename(filedata.name) # PRE UPLOAD SIGNAL - filebrowser_pre_upload.send(sender = request, path = request.POST.get('folder'), file = filedata) + filebrowser_pre_upload.send(sender=request, path=request.POST.get('folder'), file=filedata) # HANDLE UPLOAD uploadedfile = _handle_file_upload(abs_path, filedata) # MOVE UPLOADED FILE @@ -263,19 +270,20 @@ def _upload_file(request): new_file = os.path.join(abs_path, uploadedfile) file_move_safe(new_file, old_file) # POST UPLOAD SIGNAL - filebrowser_post_upload.send(sender = request, path = request.POST.get('folder'), file = FileObject(os.path.join(DIRECTORY, folder, filedata.name))) + filebrowser_post_upload.send(sender=request, path=request.POST.get('folder'), file=FileObject(os.path.join(DIRECTORY, folder, filedata.name))) return HttpResponse('True') _upload_file = flash_login_required(_upload_file) # delete signals -filebrowser_pre_delete = Signal(providing_args = ["path", "filename"]) -filebrowser_post_delete = Signal(providing_args = ["path", "filename"]) +filebrowser_pre_delete = Signal(providing_args=["path", "filename"]) +filebrowser_post_delete = Signal(providing_args=["path", "filename"]) + def delete(request): """ Delete existing File/Directory. - + When trying to delete a Directory, the Directory has to be empty. """ @@ -285,7 +293,7 @@ def delete(request): filename = _get_file(query.get('dir', ''), query.get('filename', '')) if path is None or filename is None: msg = _('Directory/File does not exist.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect(reverse("fb_browse")) abs_path = os.path.join(MEDIA_ROOT, DIRECTORY, path) @@ -295,7 +303,7 @@ def delete(request): relative_server_path = os.path.join(DIRECTORY, path, filename) try: # PRE DELETE SIGNAL - filebrowser_pre_delete.send(sender = request, path = path, filename = filename) + filebrowser_pre_delete.send(sender=request, path=path, filename=filename) # DELETE IMAGE VERSIONS/THUMBNAILS for version in VERSIONS: try: @@ -305,10 +313,10 @@ def delete(request): # DELETE FILE os.unlink(os.path.join(abs_path, filename)) # POST DELETE SIGNAL - filebrowser_post_delete.send(sender = request, path = path, filename = filename) + filebrowser_post_delete.send(sender=request, path=path, filename=filename) # MESSAGE & REDIRECT msg = _('The file %s was successfully deleted.') % (filename.lower()) - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) redirect_url = reverse("fb_browse") + query_helper(query, "", "filename,filetype") return HttpResponseRedirect(redirect_url) except OSError: @@ -317,14 +325,14 @@ def delete(request): else: try: # PRE DELETE SIGNAL - filebrowser_pre_delete.send(sender = request, path = path, filename = filename) + filebrowser_pre_delete.send(sender=request, path=path, filename=filename) # DELETE FOLDER os.rmdir(os.path.join(abs_path, filename)) # POST DELETE SIGNAL - filebrowser_post_delete.send(sender = request, path = path, filename = filename) + filebrowser_post_delete.send(sender=request, path=path, filename=filename) # MESSAGE & REDIRECT msg = _('The directory %s was successfully deleted.') % (filename.lower()) - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) redirect_url = reverse("fb_browse") + query_helper(query, "", "filename,filetype") return HttpResponseRedirect(redirect_url) except OSError: @@ -332,7 +340,7 @@ def delete(request): msg = OSError if msg: - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) return render_to_response('filebrowser/index.html', { 'dir': dir_name, @@ -340,18 +348,19 @@ def delete(request): 'query': query, 'settings_var': _get_settings_var(), 'breadcrumbs': _get_breadcrumbs(query, dir_name, ''), - }, context_instance = Context(request)) + }, context_instance=Context(request)) delete = staff_member_required(never_cache(delete)) # delete signals -filebrowser_pre_rename = Signal(providing_args = ["path", "filename"]) -filebrowser_post_rename = Signal(providing_args = ["path", "filename"]) +filebrowser_pre_rename = Signal(providing_args=["path", "filename"]) +filebrowser_post_rename = Signal(providing_args=["path", "filename"]) + def rename(request): """ Rename existing File/Directory. - + Includes renaming existing Image Versions/Thumbnails. """ @@ -363,7 +372,7 @@ def rename(request): filename = _get_file(query.get('dir', ''), query.get('filename', '')) if path is None or filename is None: msg = _('Directory/File does not exist.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect(reverse("fb_browse")) abs_path = os.path.join(MEDIA_ROOT, DIRECTORY, path) file_extension = os.path.splitext(filename)[1].lower() @@ -375,7 +384,7 @@ def rename(request): new_relative_server_path = os.path.join(DIRECTORY, path, form.cleaned_data['name'] + file_extension) try: # PRE RENAME SIGNAL - filebrowser_pre_delete.send(sender = request, path = path, filename = filename) + filebrowser_pre_delete.send(sender=request, path=path, filename=filename) # DELETE IMAGE VERSIONS/THUMBNAILS # regenerating versions/thumbs will be done automatically for version in VERSIONS: @@ -386,10 +395,10 @@ def rename(request): # RENAME ORIGINAL os.rename(os.path.join(MEDIA_ROOT, relative_server_path), os.path.join(MEDIA_ROOT, new_relative_server_path)) # POST RENAME SIGNAL - filebrowser_post_delete.send(sender = request, path = path, filename = filename) + filebrowser_post_delete.send(sender=request, path=path, filename=filename) # MESSAGE & REDIRECT msg = _('Renaming was successful.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) redirect_url = reverse("fb_browse") + query_helper(query, "", "filename") return HttpResponseRedirect(redirect_url) except OSError, (errno, strerror): @@ -404,7 +413,7 @@ def rename(request): 'title': _(u'Rename "%s"') % filename, 'settings_var': _get_settings_var(), 'breadcrumbs': _get_breadcrumbs(query, path, _(u'Rename')), - }, context_instance = Context(request)) + }, context_instance=Context(request)) rename = staff_member_required(never_cache(rename)) @@ -419,7 +428,7 @@ def versions(request): filename = _get_file(query.get('dir', ''), query.get('filename', '')) if path is None or filename is None: msg = _('Directory/File does not exist.') - request.user.message_set.create(message = msg) + request.user.message_set.create(message=msg) return HttpResponseRedirect(reverse("fb_browse")) abs_path = os.path.join(MEDIA_ROOT, DIRECTORY, path) @@ -429,7 +438,6 @@ def versions(request): 'title': _(u'Versions for "%s"') % filename, 'settings_var': _get_settings_var(), 'breadcrumbs': _get_breadcrumbs(query, path, _(u'Versions for "%s"') % filename), - }, context_instance = Context(request)) -versions = staff_member_required(never_cache(versions)) - + }, context_instance=Context(request)) +versions = staff_member_required(never_cache(versions)) diff --git a/apps/toolbar/admin.py b/apps/toolbar/admin.py index 7edd26b5..6f69fb94 100644 --- a/apps/toolbar/admin.py +++ b/apps/toolbar/admin.py @@ -5,6 +5,7 @@ from django.utils import simplejson as json from toolbar import models + class ButtonAdminForm(forms.ModelForm): class Meta: model = models.Button @@ -16,6 +17,7 @@ class ButtonAdminForm(forms.ModelForm): except ValueError, e: raise forms.ValidationError(e) + class ButtonAdmin(admin.ModelAdmin): form = ButtonAdminForm list_display = ('slug', 'label', 'tooltip', 'accesskey') @@ -25,4 +27,4 @@ class ButtonAdmin(admin.ModelAdmin): admin.site.register(models.Button, ButtonAdmin) admin.site.register(models.ButtonGroup) -admin.site.register(models.Scriptlet) \ No newline at end of file +admin.site.register(models.Scriptlet) diff --git a/apps/toolbar/management/__init__.py b/apps/toolbar/management/__init__.py index 5ff26b2a..792d6005 100644 --- a/apps/toolbar/management/__init__.py +++ b/apps/toolbar/management/__init__.py @@ -1,4 +1 @@ -# To change this template, choose Tools | Templates -# and open the template in the editor. - - +# diff --git a/apps/toolbar/management/commands/fixbuttons.py b/apps/toolbar/management/commands/fixbuttons.py index 7a49b37a..ea797fcf 100644 --- a/apps/toolbar/management/commands/fixbuttons.py +++ b/apps/toolbar/management/commands/fixbuttons.py @@ -2,24 +2,24 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # -__author__="lreqc" -__date__ ="$2009-09-08 14:31:26$" + from django.core.management.base import NoArgsCommand from toolbar.models import Button, ButtonGroup from django.utils import simplejson as json import re + class Command(NoArgsCommand): - + def handle_noargs(self, **options): buttons = Button.objects.all() print "Validating parameters... " for b in buttons: - params = b.params; + params = b.params try: - v = json.loads(b.params) + v = json.loads(b.params) except ValueError, e: print 'Trying to fix button "%s" ...' % b.slug if params[0] == u'(': @@ -43,7 +43,7 @@ class Command(NoArgsCommand): if b.slug not in hash: hash[b.slug] = b continue - + # duplicate button print "Found duplicate of '%s'" % b.slug a = hash[b.slug] @@ -66,8 +66,8 @@ class Command(NoArgsCommand): print "Searching for empty groups and orphaned buttons:" for g in ButtonGroup.objects.all(): if len(g.button_set.all()) == 0: - print "Empty group: '%s'" % g.slug - + print "Empty group: '%s'" % g.slug + for b in Button.objects.all(): if len(b.group.all()) == 0: - print "orphan: '%s'" % b.slug + print "orphan: '%s'" % b.slug diff --git a/apps/toolbar/migrations/0001_initial.py b/apps/toolbar/migrations/0001_initial.py index 974ea662..5a6f27c6 100644 --- a/apps/toolbar/migrations/0001_initial.py +++ b/apps/toolbar/migrations/0001_initial.py @@ -5,9 +5,9 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Adding model 'ButtonGroup' db.create_table('toolbar_buttongroup', ( ('position', self.gf('django.db.models.fields.IntegerField')(default=0)), @@ -45,10 +45,10 @@ class Migration(SchemaMigration): ('name', self.gf('django.db.models.fields.CharField')(max_length=64, primary_key=True)), )) db.send_create_signal('toolbar', ['Scriptlet']) - - + + def backwards(self, orm): - + # Deleting model 'ButtonGroup' db.delete_table('toolbar_buttongroup') @@ -60,8 +60,8 @@ class Migration(SchemaMigration): # Deleting model 'Scriptlet' db.delete_table('toolbar_scriptlet') - - + + models = { 'toolbar.button': { 'Meta': {'object_name': 'Button'}, @@ -89,5 +89,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) } } - + complete_apps = ['toolbar'] diff --git a/apps/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py b/apps/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py index 09ddd323..af58c6ec 100644 --- a/apps/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py +++ b/apps/toolbar/migrations/0002_auto__del_field_button_key_mod__chg_field_button_key.py @@ -5,23 +5,23 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Deleting field 'Button.key_mod' db.delete_column('toolbar_button', 'key_mod') # Changing field 'Button.key' db.alter_column('toolbar_button', 'key', self.gf('django.db.models.fields.CharField')(max_length=1, null=True)) - + def backwards(self, orm): - + # Adding field 'Button.key_mod' db.add_column('toolbar_button', 'key_mod', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, blank=True), keep_default=False) # Changing field 'Button.key' - db.alter_column('toolbar_button', 'key', self.gf('django.db.models.fields.CharField')(max_length=1, blank=True)) - + db.alter_column('toolbar_button', 'key', self.gf('django.db.models.fields.CharField')(max_length=1, blank=True)) + models = { 'toolbar.button': { 'Meta': {'object_name': 'Button'}, @@ -48,5 +48,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) } } - + complete_apps = ['toolbar'] diff --git a/apps/toolbar/migrations/0003_button_key_rename_to_accesskey.py b/apps/toolbar/migrations/0003_button_key_rename_to_accesskey.py index 0d2e9068..7a0961d9 100644 --- a/apps/toolbar/migrations/0003_button_key_rename_to_accesskey.py +++ b/apps/toolbar/migrations/0003_button_key_rename_to_accesskey.py @@ -5,18 +5,18 @@ from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - + # Deleting field 'Button.key' db.rename_column('toolbar_button', 'key', 'accesskey') - - - def backwards(self, orm): + + + def backwards(self, orm): db.rename_column('toolbar_button', 'accesskey', 'key') - - + + models = { 'toolbar.button': { 'Meta': {'object_name': 'Button'}, @@ -43,5 +43,5 @@ class Migration(SchemaMigration): 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'primary_key': 'True'}) } } - + complete_apps = ['toolbar'] diff --git a/apps/toolbar/models.py b/apps/toolbar/models.py index d736c027..3c8ff4f1 100644 --- a/apps/toolbar/models.py +++ b/apps/toolbar/models.py @@ -1,16 +1,16 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.db import models from django.utils.translation import ugettext_lazy as _ class ButtonGroup(models.Model): - name = models.CharField(max_length = 32) + name = models.CharField(max_length=32) slug = models.SlugField() - position = models.IntegerField(default = 0) + position = models.IntegerField(default=0) class Meta: ordering = ('position', 'name',) @@ -19,49 +19,31 @@ class ButtonGroup(models.Model): def __unicode__(self): return self.name - def to_dict(self, with_buttons = False): + def to_dict(self, with_buttons=False): d = {'name': self.name, 'position': self.position} if with_buttons: - d['buttons'] = [ b.to_dict() for b in self.button_set.all() ] + d['buttons'] = [b.to_dict() for b in self.button_set.all()] return d -#class ButtonGroupManager(models.Manager): -# -# def with_buttons(self): -# from django.db import connection -# cursor = connection.cursor() -# cursor.execute(""" -# SELECT g.name, g.slug, CONCAT(b.slug), -# FROM toolbar_buttongroup as g LEFT JOIN toolbar_button as b -# -# WHERE p.id = r.poll_id -# GROUP BY 1, 2, 3 -# ORDER BY 3 DESC""") -# result_list = [] -# for row in cursor.fetchall(): -# p = self.model(id=row[0], question=row[1], poll_date=row[2]) -# p.num_responses = row[3] -# result_list.append(p) -# return result_list class Button(models.Model): - label = models.CharField(max_length = 32) - slug = models.SlugField(unique = True) #unused + label = models.CharField(max_length=32) + slug = models.SlugField(unique=True) # unused # behaviour - params = models.TextField(default = '[]') # TODO: should be a JSON field - scriptlet = models.ForeignKey('Scriptlet', null = True, blank = True) - link = models.CharField(max_length = 256, blank = True, default = '') + params = models.TextField(default='[]') # TODO: should be a JSON field + scriptlet = models.ForeignKey('Scriptlet', null=True, blank=True) + link = models.CharField(max_length=256, blank=True, default='') # ui related stuff - accesskey = models.CharField(null = True, max_length = 1) - - tooltip = models.CharField(blank = True, max_length = 120) + accesskey = models.CharField(null=True, max_length=1) + + tooltip = models.CharField(blank=True, max_length=120) # Why the button is restricted to have the same position in each group ? - # position = models.IntegerField(default=0) + # position = models.IntegerField(default=0) group = models.ManyToManyField(ButtonGroup) class Meta: @@ -76,16 +58,17 @@ class Button(models.Model): return { 'label': self.label, 'tooltip': self.tooltip, - 'accesskey': self.accesskey, + 'accesskey': self.accesskey, 'params': self.params, - 'scriptlet_id': self.scriptlet_id + 'scriptlet_id': self.scriptlet_id, } def __unicode__(self): return self.label + class Scriptlet(models.Model): - name = models.CharField(max_length = 64, primary_key = True) + name = models.CharField(max_length=64, primary_key=True) code = models.TextField() def __unicode__(self): diff --git a/apps/toolbar/templatetags/toolbar_tags.py b/apps/toolbar/templatetags/toolbar_tags.py index 421709e9..0766677e 100644 --- a/apps/toolbar/templatetags/toolbar_tags.py +++ b/apps/toolbar/templatetags/toolbar_tags.py @@ -1,16 +1,18 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django import template from toolbar import models register = template.Library() + @register.inclusion_tag('toolbar/toolbar.html') def toolbar(): - return {'toolbar_groups': models.ButtonGroup.objects.all().select_related() } + return {'toolbar_groups': models.ButtonGroup.objects.all().select_related()} + @register.inclusion_tag('toolbar/button.html') def toolbar_button(b): diff --git a/apps/wiki/forms.py b/apps/wiki/forms.py index 236c3f0e..2bd6e64e 100644 --- a/apps/wiki/forms.py +++ b/apps/wiki/forms.py @@ -1,21 +1,21 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django import forms from wiki.models import Document, getstorage -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _ class DocumentForm(forms.Form): """ Old form for saving document's text """ - + name = forms.CharField(widget=forms.HiddenInput) text = forms.CharField(widget=forms.Textarea) revision = forms.IntegerField(widget=forms.HiddenInput) comment = forms.CharField() - + def __init__(self, *args, **kwargs): document = kwargs.pop('instance', None) super(DocumentForm, self).__init__(*args, **kwargs) @@ -23,65 +23,66 @@ class DocumentForm(forms.Form): self.fields['name'].initial = document.name self.fields['text'].initial = document.text self.fields['revision'].initial = document.revision() - - def save(self, document_author = 'anonymous'): + + def save(self, document_author='anonymous'): storage = getstorage() - + document = Document(storage, name=self.cleaned_data['name'], text=self.cleaned_data['text']) - - storage.put(document, - author = document_author, - comment = self.cleaned_data['comment'], - parent =self.cleaned_data['revision'] ) - + + storage.put(document, + author=document_author, + comment=self.cleaned_data['comment'], + parent=self.cleaned_data['revision']) + return storage.get(self.cleaned_data['name']) - + + class DocumentTagForm(forms.Form): TAGS = ( - ("publish", "Do publikacji"), + ("publish", "Do publikacji"), ) - - tag = forms.ChoiceField(choices = TAGS) - version = forms.IntegerField(widget = forms.HiddenInput) + + tag = forms.ChoiceField(choices=TAGS) + version = forms.IntegerField(widget=forms.HiddenInput) + class DocumentTextSaveForm(forms.Form): - """ + """ Form for saving document's text: - + * name - document's storage identifier. * parent_revision - revision which the modified text originated from. * comment - user's verbose comment; will be used in commit. * stage_completed - mark this change as end of given stage. - + """ DOC_STAGES = ( ('', 'Nic konkretnego'), ('tagging', 'Tagowanie'), ('modernized', 'Uwspółcześnienia'), ('editing', 'Redakcja'), - ) - + ) + id = forms.CharField(widget=forms.HiddenInput) parent_revision = forms.IntegerField(widget=forms.HiddenInput) text = forms.CharField(widget=forms.HiddenInput) - + author = forms.CharField( - required = False, - label = _(u"Autor"), - help_text = _(u"Twoje imie i nazwisko lub email.") + required=False, + label=_(u"Autor"), + help_text=_(u"Twoje imie i nazwisko lub email."), ) - + comment = forms.CharField( - required = True, + required=True, widget=forms.Textarea, - label = _(u"Twój komentarz"), - help_text = _(u"Opisz w miarę dokładnie swoje zmiany."), + label=_(u"Twój komentarz"), + help_text=_(u"Opisz w miarę dokładnie swoje zmiany."), ) - + stage_completed = forms.ChoiceField( - choices=DOC_STAGES, - required= False, - label = _(u"Skończyłem robić"), - help_text = _(u"Jeśli skończyłeś jeden z etapów utworu, wybierz go."), + choices=DOC_STAGES, + required=False, + label=_(u"Skończyłem robić"), + help_text=_(u"Jeśli skończyłeś jeden z etapów utworu, wybierz go."), ) - \ No newline at end of file diff --git a/apps/wiki/helpers.py b/apps/wiki/helpers.py index 7ed97b41..d4daf1ad 100644 --- a/apps/wiki/helpers.py +++ b/apps/wiki/helpers.py @@ -4,38 +4,38 @@ from django.utils.functional import Promise from django.template.loader import render_to_string from datetime import datetime + class ExtendedEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, Promise): - return unicode(obj) - + return unicode(obj) + if isinstance(obj, datetime): return datetime.ctime(obj) + " " + (datetime.tzname(obj) or 'GMT') - + return json.JSONEncoder.default(self, obj) + # shortcut for JSON reponses class JSONResponse(http.HttpResponse): - - def __init__(self, data = {}, **kwargs): + + def __init__(self, data={}, **kwargs): # get rid of mimetype kwargs.pop('mimetype', None) - + super(JSONResponse, self).__init__( - json.dumps(data, cls=ExtendedEncoder), - mimetype = "application/json", **kwargs) - + json.dumps(data, cls=ExtendedEncoder), + mimetype="application/json", **kwargs) + # return errors class JSONFormInvalid(JSONResponse): - def __init__(self, form): - super(JSONFormInvalid, self).__init__(form.errors, status = 400) - -class JSONServerError(JSONResponse): + def __init__(self, form): + super(JSONFormInvalid, self).__init__(form.errors, status=400) + + +class JSONServerError(JSONResponse): def __init__(self, *args, **kwargs): kwargs['status'] = 500 super(JSONServerError, self).__init__(*args, **kwargs) - - - \ No newline at end of file diff --git a/apps/wiki/models.py b/apps/wiki/models.py index a530916f..e4ac7289 100644 --- a/apps/wiki/models.py +++ b/apps/wiki/models.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import re import os @@ -14,37 +14,38 @@ from django.http import Http404 import logging logger = logging.getLogger("fnp.wiki") + class DocumentStorage(object): def __init__(self, path): self.vstorage = vstorage.VersionedStorage(path) - def get(self, name, revision = None): + def get(self, name, revision=None): if revision is None: text = self.vstorage.page_text(name) else: text = self.vstorage.revision_text(name, revision) - return Document(self, name = name, text = text) - + return Document(self, name=name, text=text) + def get_or_404(self, *args, **kwargs): try: return self.get(*args, **kwargs) except DocumentNotFound: - raise Http404 + raise Http404 def put(self, document, author, comment, parent): self.vstorage.save_text( - title = document.name, - text = document.text, - author = author, - comment = comment, - parent = parent) + title=document.name, + text=document.text, + author=author, + comment=comment, + parent=parent) def delete(self, name, author, comment): self.vstorage.delete_page(name, author, comment) def all(self): return list(self.vstorage.all_pages()) - + def history(self, title): return list(self.vstorage.page_history(title)) @@ -64,8 +65,8 @@ class Document(object): try: return self.storage._info(self.name)[0] except DocumentNotFound: - return -1 - + return - 1 + def add_tag(self, tag): """ Add document specific tag """ logger.debug("Adding tag %s to doc %s version %d", tag, self.name, self.revision) @@ -85,25 +86,26 @@ class Document(object): k, v = line.split(':', 1) result[k.strip()] = v.strip() except ValueError: - continue - + continue + gallery = result.get('gallery', self.name.replace(' ', '_')) - - if gallery.startswith('/'): + + if gallery.startswith('/'): gallery = os.path.basename(gallery) - + result['gallery'] = gallery - + if 'title' not in result: - result['title'] = self.name.title() + result['title'] = self.name.title() return result - + def info(self): return dict(zip( ('revision', 'last_update', 'last_comitter', 'commit_message'), - self.storage._info(self.name) - )) + self.storage._info(self.name), + )) + def getstorage(): return DocumentStorage(settings.REPOSITORY_PATH) diff --git a/apps/wiki/nice_diff.py b/apps/wiki/nice_diff.py index c000b13c..6f6e6071 100755 --- a/apps/wiki/nice_diff.py +++ b/apps/wiki/nice_diff.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import difflib import re @@ -9,21 +9,23 @@ import re from django.template.loader import render_to_string from django.utils.html import escape as html_escape -DIFF_RE = re.compile(r"""\x00([+^-])""" ,re.UNICODE) -NAMES = { '+': 'added', '-': 'removed', '^': 'changed' } +DIFF_RE = re.compile(r"""\x00([+^-])""", re.UNICODE) +NAMES = {'+': 'added', '-': 'removed', '^': 'changed'} + def diff_replace(match): - return """""" % NAMES[match.group(1)] + return """""" % NAMES[match.group(1)] + def filter_line(line): - return DIFF_RE.sub(diff_replace, html_escape(line)).replace('\x01', '') + return DIFF_RE.sub(diff_replace, html_escape(line)).replace('\x01', '') + def html_diff_table(la, lb): - return render_to_string("wiki/diff_table.html", { - "changes": [(a[0],filter_line(a[1]), - b[0],filter_line(b[1]), - change) for a, b, change in difflib._mdiff(la, lb)] - }); - - -__all__ = ['html_diff_table'] \ No newline at end of file + return render_to_string("wiki/diff_table.html", { + "changes": [(a[0], filter_line(a[1]), b[0], filter_line(b[1]), change) + for a, b, change in difflib._mdiff(la, lb)], + }) + + +__all__ = ['html_diff_table'] diff --git a/apps/wiki/tests.py b/apps/wiki/tests.py index 91eccc8a..65777379 100644 --- a/apps/wiki/tests.py +++ b/apps/wiki/tests.py @@ -1,20 +1,19 @@ from nose.tools import * import wiki.models as models -import shutil, tempfile +import shutil +import tempfile + class TestStorageBase: def setUp(self): - self.dirpath = tempfile.mkdtemp(prefix = 'nosetest_') + self.dirpath = tempfile.mkdtemp(prefix='nosetest_') def tearDown(self): shutil.rmtree(self.dirpath) + class TestDocumentStorage(TestStorageBase): def test_storage_empty(self): storage = models.DocumentStorage(self.dirpath) eq_(storage.all(), []) - - - - diff --git a/apps/wiki/urls.py b/apps/wiki/urls.py index b319465c..fa4add39 100644 --- a/apps/wiki/urls.py +++ b/apps/wiki/urls.py @@ -1,19 +1,19 @@ from django.conf.urls.defaults import * from django.conf import settings -urlpatterns = patterns('wiki.views', - url(r'^$', +urlpatterns = patterns('wiki.views', + url(r'^$', 'document_list', name='wiki_doclist'), - url(r'^gallery/(?P[^/]+)$', + url(r'^gallery/(?P[^/]+)$', 'document_gallery', name="wiki_gallery"), - url(r'^(?P[^/]+)/history$', + url(r'^(?P[^/]+)/history$', 'document_history', name="wiki_history"), - url(r'^(?P[^/]+)/text$', + url(r'^(?P[^/]+)/text$', 'document_text', name="wiki_text"), - url(r'^(?P[^/]+)/publish/(?P\d+)$', + url(r'^(?P[^/]+)/publish/(?P\d+)$', 'document_publish', name="wiki_publish"), - url(r'^(?P[^/]+)/diff$', - 'document_diff', name="wiki_diff"), - url(r'^(?P[^/]+)$', - 'document_detail', name="wiki_details"), -) \ No newline at end of file + url(r'^(?P[^/]+)/diff$', + 'document_diff', name="wiki_diff"), + url(r'^(?P[^/]+)$', + 'document_detail', name="wiki_details"), +) diff --git a/apps/wiki/views.py b/apps/wiki/views.py index 29039b71..64e93305 100644 --- a/apps/wiki/views.py +++ b/apps/wiki/views.py @@ -25,68 +25,70 @@ import operator MAX_LAST_DOCS = 10 + @never_cache -def document_list(request, template_name = 'wiki/document_list.html'): +def document_list(request, template_name='wiki/document_list.html'): # TODO: find a way to cache "Storage All" - return direct_to_template(request, template_name, extra_context = { + return direct_to_template(request, template_name, extra_context={ 'document_list': getstorage().all(), - 'last_docs': sorted(request.session.get("wiki_last_docs", {}).items(), - key=operator.itemgetter(1), reverse = True) - }) + 'last_docs': sorted(request.session.get("wiki_last_docs", {}).items(), + key=operator.itemgetter(1), reverse=True), + }) + @never_cache -def document_detail(request, name, template_name = 'wiki/document_details.html'): - +def document_detail(request, name, template_name='wiki/document_details.html'): + document = getstorage().get_or_404(name) - + access_time = datetime.now() - last_documents = request.session.get("wiki_last_docs", {}) + last_documents = request.session.get("wiki_last_docs", {}) last_documents[name] = access_time - + if len(last_documents) > MAX_LAST_DOCS: - oldest_key = min(last_documents, key = last_documents.__getitem__) - del last_documents[oldest_key] - request.session['wiki_last_docs'] = last_documents + oldest_key = min(last_documents, key=last_documents.__getitem__) + del last_documents[oldest_key] + request.session['wiki_last_docs'] = last_documents - return direct_to_template(request, template_name, extra_context = { + return direct_to_template(request, template_name, extra_context={ 'document': document, 'document_info': document.info, 'document_meta': document.meta, - 'forms': {"text_save": DocumentTextSaveForm(), "add_tag": DocumentTagForm() }, + 'forms': {"text_save": DocumentTextSaveForm(), "add_tag": DocumentTagForm()}, }) + @never_cache -def document_text(request, name): +def document_text(request, name): storage = getstorage() - document = storage.get_or_404(name) - - if request.method == 'POST': + document = storage.get_or_404(name) + + if request.method == 'POST': form = DocumentTextSaveForm(request.POST) - - if form.is_valid(): + + if form.is_valid(): revision = form.cleaned_data['parent_revision'] document.text = form.cleaned_data['text'] - - storage.put(document, - author = form.cleaned_data['author'] or request.user.username, - comment = form.cleaned_data['comment'], - parent = revision + + storage.put(document, + author=form.cleaned_data['author'] or request.user.username, + comment=form.cleaned_data['comment'], + parent=revision, ) - + return JSONResponse({ - 'text': document.plain_text if revision != document.revision() else None, - 'meta': document.meta(), - 'revision': document.revision() + 'text': document.plain_text if revision != document.revision() else None, + 'meta': document.meta(), + 'revision': document.revision(), }) else: - return JSONFormInvalid(form) + return JSONFormInvalid(form) else: return JSONResponse({ - 'text': document.plain_text, - 'meta': document.meta(), - 'revision': document.revision() + 'text': document.plain_text, + 'meta': document.meta(), + 'revision': document.revision(), }) - @never_cache @@ -96,71 +98,75 @@ def document_gallery(request, directory): smart_unicode(settings.MEDIA_URL), smart_unicode(settings.FILEBROWSER_DIRECTORY), smart_unicode(directory))) - + base_dir = os.path.join( - smart_unicode(settings.MEDIA_ROOT), + smart_unicode(settings.MEDIA_ROOT), smart_unicode(settings.FILEBROWSER_DIRECTORY), smart_unicode(directory)) - + def map_to_url(filename): return "%s/%s" % (base_url, smart_unicode(filename)) - + def is_image(filename): return os.path.splitext(f)[1].lower() in (u'.jpg', u'.jpeg', u'.png') - - images = [ map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f) ] + + images = [map_to_url(f) for f in map(smart_unicode, os.listdir(base_dir)) if is_image(f)] images.sort() return JSONResponse(images) except (IndexError, OSError), exc: import traceback traceback.print_exc() raise http.Http404 - + + @never_cache def document_diff(request, name): - storage = getstorage() - + storage = getstorage() + revA = int(request.GET.get('from', 0)) revB = int(request.GET.get('to', 0)) - + if revA > revB: revA, revB = revB, revA - + if revB == 0: - revB = None - + revB = None + docA = storage.get_or_404(name, int(revA)) - docB = storage.get_or_404(name, int(revB)) - - return http.HttpResponse(nice_diff.html_diff_table(docA.plain_text.splitlines(), - docB.plain_text.splitlines()) ) - -@never_cache + docB = storage.get_or_404(name, int(revB)) + + return http.HttpResponse(nice_diff.html_diff_table(docA.plain_text.splitlines(), + docB.plain_text.splitlines())) + + +@never_cache def document_history(request, name): storage = getstorage() - return JSONResponse(storage.history(name)) + return JSONResponse(storage.history(name)) + @require_POST -def document_add_tag(request, name): +def document_add_tag(request, name): storage = getstorage() - + form = DocumentTagForm(request.POST) if form.is_valid(): doc = storage.get_or_404(name, form.cleaned_data['version']) doc.add_tag(form.cleaned_data['tag']) - return JSONResponse({"message": _("Tag added")}) + return JSONResponse({"message": _("Tag added")}) else: return JSONFormInvalid(form) - + + @require_POST def document_publish(request, name, version): storage = getstorage() - + # get the document - document = storage.get_or_404(name, revision = int(version)) - - api = wlapi.WLAPI(settings.WL_API_CONFIG) - try: + document = storage.get_or_404(name, revision=int(version)) + + api = wlapi.WLAPI(settings.WL_API_CONFIG) + try: return JSONResponse({"result": api.publish_book(document)}) - except wlapi.APICallException, e: - return JSONServerError({"message": str(e)}) \ No newline at end of file + except wlapi.APICallException, e: + return JSONServerError({"message": str(e)}) diff --git a/fabfile.py b/fabfile.py index 724fca2a..587821b5 100644 --- a/fabfile.py +++ b/fabfile.py @@ -1,4 +1,4 @@ -from __future__ import with_statement # needed for python 2.5 +from __future__ import with_statement # needed for python 2.5 from fabric.api import * from fabric.contrib import files @@ -12,6 +12,7 @@ import os env.project_name = 'platforma' env.use_south = True + # Servers def staging(): """Use staging server""" @@ -22,6 +23,7 @@ def staging(): env.virtualenv = '/usr/bin/virtualenv' env.pip = '/usr/bin/pip' + def production(): """Use production server""" env.hosts = ['szo.nowoczesnapolska.org.pl:2225'] @@ -37,28 +39,30 @@ def production(): # ========= def test(): "Run the test suite and bail out if it fails" - require('hosts', 'path', provided_by = [staging, production]) + require('hosts', 'path', provided_by=[staging, production]) result = run('cd %(path)s/%(project_name)s; %(python)s manage.py test' % env) + def setup(): """ Setup a fresh virtualenv as well as a few useful directories, then run a full deployment. virtualenv and pip should be already installed. """ - require('hosts', 'path', provided_by = [staging, production]) + require('hosts', 'path', provided_by=[staging, production]) - run('mkdir -p %(path)s; cd %(path)s; %(virtualenv)s --no-site-packages .;' % env, pty = True) - run('cd %(path)s; mkdir releases; mkdir shared; mkdir packages;' % env, pty = True) - run('cd %(path)s/releases; ln -s . current; ln -s . previous' % env, pty = True) + run('mkdir -p %(path)s; cd %(path)s; %(virtualenv)s --no-site-packages .;' % env, pty=True) + run('cd %(path)s; mkdir releases; mkdir shared; mkdir packages;' % env, pty=True) + run('cd %(path)s/releases; ln -s . current; ln -s . previous' % env, pty=True) deploy() + def deploy(): """ - Deploy the latest version of the site to the servers, - install any required third party modules, + Deploy the latest version of the site to the servers, + install any required third party modules, install the virtual host and then restart the webserver """ - require('hosts', 'path', provided_by = [staging, production]) + require('hosts', 'path', provided_by=[staging, production]) import time env.release = time.strftime('%Y-%m-%dT%H%M') @@ -73,26 +77,28 @@ def deploy(): django_compress() restart_webserver() + def deploy_version(version): "Specify a specific version to be made live" - require('hosts', 'path', provided_by = [localhost, webserver]) + require('hosts', 'path', provided_by=[localhost, webserver]) env.version = version with cd(env.path): - run('rm releases/previous; mv releases/current releases/previous;', pty = True) - run('ln -s %(version)s releases/current' % env, pty = True) + run('rm releases/previous; mv releases/current releases/previous;', pty=True) + run('ln -s %(version)s releases/current' % env, pty=True) restart_webserver() + def rollback(): """ Limited rollback capability. Simple loads the previously current version of the code. Rolling back again will swap between the two. """ - require('hosts', provided_by = [staging, production]) + require('hosts', provided_by=[staging, production]) require('path') with cd(env.path): - run('mv releases/current releases/_previous;', pty = True) - run('mv releases/previous releases/current;', pty = True) - run('mv releases/_previous releases/previous;', pty = True) + run('mv releases/current releases/_previous;', pty=True) + run('mv releases/previous releases/current;', pty=True) + run('mv releases/_previous releases/previous;', pty=True) restart_webserver() @@ -102,65 +108,72 @@ def rollback(): def upload_tar_from_git(): "Create an archive from the current Git master branch and upload it" print '>>> upload tar from git' - require('release', provided_by = [deploy]) + require('release', provided_by=[deploy]) local('git archive --format=tar master | gzip > %(release)s.tar.gz' % env) - run('mkdir -p %(path)s/releases/%(release)s' % env, pty = True) - run('mkdir -p %(path)s/packages' % env, pty = True) + run('mkdir -p %(path)s/releases/%(release)s' % env, pty=True) + run('mkdir -p %(path)s/packages' % env, pty=True) put('%(release)s.tar.gz' % env, '%(path)s/packages/' % env) - run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty = True) + run('cd %(path)s/releases/%(release)s && tar zxf ../../packages/%(release)s.tar.gz' % env, pty=True) local('rm %(release)s.tar.gz' % env) + def upload_vhost_sample(): "Create and upload Apache virtual host configuration sample" print ">>> upload vhost sample" - files.upload_template('%(project_name)s.vhost.template' % env, '%(path)s/%(project_name)s.vhost.sample' % env, context = env) + files.upload_template('%(project_name)s.vhost.template' % env, '%(path)s/%(project_name)s.vhost.sample' % env, context=env) + def upload_wsgi_script(): "Create and upload a wsgi script sample" print ">>> upload wsgi script sample" - files.upload_template('%(project_name)s.wsgi.template' % env, '%(path)s/%(project_name)s.wsgi' % env, context = env) + files.upload_template('%(project_name)s.wsgi.template' % env, '%(path)s/%(project_name)s.wsgi' % env, context=env) run('chmod ug+x %(path)s/%(project_name)s.wsgi' % env) + def install_requirements(): "Install the required packages from the requirements file using pip" print '>>> install requirements' - require('release', provided_by = [deploy]) - run('cd %(path)s; %(pip)s install -E . -r %(path)s/releases/%(release)s/requirements.txt' % env, pty = True) + require('release', provided_by=[deploy]) + run('cd %(path)s; %(pip)s install -E . -r %(path)s/releases/%(release)s/requirements.txt' % env, pty=True) + def copy_localsettings(): "Copy localsettings.py from root directory to release directory (if this file exists)" print ">>> copy localsettings" - require('release', provided_by = [deploy]) - require('path', provided_by = [staging, production]) + require('release', provided_by=[deploy]) + require('path', provided_by=[staging, production]) - with settings(warn_only = True): + with settings(warn_only=True): run('cp %(path)s/localsettings.py %(path)s/releases/%(release)s/%(project_name)s' % env) + def symlink_current_release(): "Symlink our current release" print '>>> symlink current release' - require('release', provided_by = [deploy]) - require('path', provided_by = [staging, production]) + require('release', provided_by=[deploy]) + require('path', provided_by=[staging, production]) with cd(env.path): run('rm releases/previous; mv releases/current releases/previous') run('ln -s %(release)s releases/current' % env) + def migrate(): "Update the database" print '>>> migrate' - require('project_name', provided_by = [staging, production]) + require('project_name', provided_by=[staging, production]) with cd('%(path)s/releases/current/%(project_name)s' % env): - run('../../../bin/python manage.py syncdb --noinput' % env, pty = True) + run('../../../bin/python manage.py syncdb --noinput' % env, pty=True) if env.use_south: - run('../../../bin/python manage.py migrate' % env, pty = True) - + run('../../../bin/python manage.py migrate' % env, pty=True) + + def django_compress(): "Update static files" print '>>> migrate' - require('project_name', provided_by = [staging, production]) + require('project_name', provided_by=[staging, production]) with cd('%(path)s/releases/current/%(project_name)s' % env): - run('../../../bin/python manage.py synccompress --force' % env, pty = True) - + run('../../../bin/python manage.py synccompress --force' % env, pty=True) + def restart_webserver(): "Restart the web server" diff --git a/lib/test_vstorage.py b/lib/test_vstorage.py index eaea46f4..23375f06 100644 --- a/lib/test_vstorage.py +++ b/lib/test_vstorage.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import os @@ -12,6 +12,8 @@ from nose.core import runmodule import vstorage +NULL_PARENT = -1 + def clear_directory(top): for root, dirs, files in os.walk(top, topdown=False): @@ -29,20 +31,24 @@ class TestVersionedStorage(object): def setUp(self): self.repo_path = tempfile.mkdtemp() self.repo = vstorage.VersionedStorage(self.repo_path) - + def tearDown(self): clear_directory(self.repo_path) - + def test_save_text(self): text = u"test text" title = u"test title" author = u"test author" comment = u"test comment" - - self.repo.save_text(title = title, - text = text, author = author, - comment = comment, parent=-1) - + + self.repo.save_text( + title=title, + text=text, + author=author, + comment=comment, + parent=NULL_PARENT, + ) + saved = self.repo.open_page(title).read() assert_equal(saved, text) @@ -51,11 +57,11 @@ class TestVersionedStorage(object): title = u"test title" author = u"test author" comment = u"test comment" - - self.repo.save_text(title = title, - text = text, author = author, - comment = comment, parent=None) - + + self.repo.save_text(title=title, + text=text, author=author, + comment=comment, parent=None) + saved = self.repo.open_page(title).read() assert_equal(saved, text) @@ -64,15 +70,15 @@ class TestVersionedStorage(object): title = u"test title" author = u"test author" comment = u"test comment" - self.repo.save_text(title = title, - text = text, author = author, - comment = comment, parent=-1) - self.repo.save_text(title = title, - text = text, author = author, - comment = comment, parent=-1) + self.repo.save_text(title=title, + text=text, author=author, + comment=comment, parent=NULL_PARENT) + self.repo.save_text(title=title, + text=text, author=author, + comment=comment, parent=NULL_PARENT) saved = self.repo.open_page(title).read() assert_equal(saved, text) - + def test_save_merge_line_conflict(self): text = u"test\ntest\n" text1 = u"test\ntext\n" @@ -80,21 +86,21 @@ class TestVersionedStorage(object): title = u"test title" author = u"test author" comment = u"test comment" - - self.repo.save_text(title = title, - text = text, author = author, - comment = comment, parent=-1) - - self.repo.save_text(title = title, - text = text1, author = author, - comment = comment, parent=0) - - self.repo.save_text(title = title, - text = text2, author = author, - comment = comment, parent=0) - + + self.repo.save_text(title=title, + text=text, author=author, + comment=comment, parent=NULL_PARENT) + + self.repo.save_text(title=title, + text=text1, author=author, + comment=comment, parent=0) + + self.repo.save_text(title=title, + text=text2, author=author, + comment=comment, parent=0) + saved = self.repo.open_page(title).read() - + # Other conflict markers placement can also be correct assert_equal(saved, u'''\ text @@ -105,20 +111,19 @@ text >>>>>>> other ''') - def test_delete(self): text = u"text test" title = u"test title" author = u"test author" comment = u"test comment" - self.repo.save_text(title = title, - text = text, author = author, - comment = comment, parent=-1) - + self.repo.save_text(title=title, + text=text, author=author, + comment=comment, parent=NULL_PARENT) + assert title in self.repo - + self.repo.delete_page(title, author, comment) - + assert title not in self.repo @raises(vstorage.DocumentNotFound) @@ -126,90 +131,88 @@ text self.repo.open_page(u'unknown entity') def test_open_existing_repository(self): - self.repo.save_text(title = u'Python!', text = u'ham and spam') + self.repo.save_text(title=u'Python!', text=u'ham and spam') current_repo_revision = self.repo.repo_revision() same_repo = vstorage.VersionedStorage(self.repo_path) assert_equal(same_repo.repo_revision(), current_repo_revision) - - + def test_history(self): COMMITS = [ - {"author": "bunny", "text":"1", "comment": "Oh yeah!"}, + {"author": "bunny", "text":"1", "comment": "Oh yeah!"}, {"author": "frank", "text":"2", "comment": "Second is the best!"}, - {"text":"3", "comment": "Third"}, # anonymous - {"author": "welma", "text":"4", "comment": "Fourth"}, + {"text":"3", "comment": "Third"}, + {"author": "welma", "text":"4", "comment": "Fourth"}, ] - + for commit in COMMITS: - self.repo.save_text(title = u"Sample", **commit) - + self.repo.save_text(title=u"Sample", **commit) + for n, entry in enumerate(reversed(list(self.repo.page_history(u"Sample")))): assert_equal(entry["version"], n) - assert_equal(entry["author"], COMMITS[n].get("author", "anonymous") ) + assert_equal(entry["author"], COMMITS[n].get("author", "anonymous")) assert_equal(entry["description"], COMMITS[n]["comment"]) - assert_equal(entry["tag"], []) - - + assert_equal(entry["tag"], []) + + class TestVSTags(object): - + TITLE_1 = "Sample" - + COMMITS = [ - {"author": "bunny", "text":"1", "comment": "Oh yeah!"}, + {"author": "bunny", "text":"1", "comment": "Oh yeah!"}, {"author": "frank", "text":"2", "comment": "Second is the best!"}, - {"text":"3", "comment": "Third"}, # anonymous - {"author": "welma", "text":"4", "comment": "Fourth"}, - ] - + {"text":"3", "comment": "Third"}, + {"author": "welma", "text":"4", "comment": "Fourth"}, + ] + def setUp(self): self.repo_path = tempfile.mkdtemp() self.repo = vstorage.VersionedStorage(self.repo_path) - + # generate some history for commit in self.COMMITS: - self.repo.save_text(title = u"Sample", **commit) - + self.repo.save_text(title=u"Sample", **commit) + # verify for n, entry in enumerate(reversed(list(self.repo.page_history(self.TITLE_1)))): assert_equal(entry["tag"], []) - + def tearDown(self): clear_directory(self.repo_path) - - def test_add_tag(self): + + def test_add_tag(self): TAG_USER = "mike_the_tagger" TAG_NAME = "production" TAG_VERSION = 2 - + # Add tag self.repo.add_page_tag(self.TITLE_1, TAG_VERSION, TAG_NAME, TAG_USER) - + # check history again history = list(self.repo.page_history(self.TITLE_1)) for entry in reversed(history): if entry["version"] == TAG_VERSION: assert_equal(entry["tag"], [TAG_NAME]) else: - assert_equal(entry["tag"], []) - - def test_add_many_tags(self): + assert_equal(entry["tag"], []) + + def test_add_many_tags(self): TAG_USER = "mike_the_tagger" tags = [ (2, "production", "mike"), (2, "finished", "jeremy"), (0, "original", "jeremy"), ] - - + for rev, name, user in tags: self.repo.add_page_tag(self.TITLE_1, rev, name, user) - + # check history again history = list(self.repo.page_history(self.TITLE_1)) for entry in reversed(history): - expected = [ tag[1] for tag in tags if tag[0] == entry["version"] ] + expected = [tag[1] for tag in tags if tag[0] == entry["version"]] assert_equal(set(entry["tag"]), set(expected)) if __name__ == '__main__': - runmodule() \ No newline at end of file + runmodule() diff --git a/lib/vstorage.py b/lib/vstorage.py index 319a0f12..ae4d22f9 100644 --- a/lib/vstorage.py +++ b/lib/vstorage.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import os import tempfile @@ -23,9 +23,9 @@ import mercurial.revlog import mercurial.util -def urlquote(url, safe = '/'): - """Quotes URL - +def urlquote(url, safe='/'): + """Quotes URL + >>> urlquote(u'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144') 'Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84' """ @@ -33,8 +33,8 @@ def urlquote(url, safe = '/'): def urlunquote(url): - """Unqotes URL - + """Unqotes URL + # >>> urlunquote('Za%C5%BC%C3%B3%C5%82%C4%87_g%C4%99%C5%9Bl%C4%85_ja%C5%BA%C5%84') # u'Za\u017c\xf3\u0142\u0107 g\u0119\u015bl\u0105 ja\u017a\u0144' """ @@ -59,10 +59,11 @@ def with_working_copy_locked(func): wlock = self.repo.wlock() try: return func(self, *args, **kwargs) - finally: + finally: wlock.release() return wrapped + def with_storage_locked(func): """A decorator for locking the repository when calling a method.""" @@ -72,10 +73,11 @@ def with_storage_locked(func): lock = self.repo.lock() try: return func(self, *args, **kwargs) - finally: + finally: lock.release() return wrapped + def guess_mime(file_name): """ Guess file's mime type based on extension. @@ -95,7 +97,7 @@ def guess_mime(file_name): 'archive/gzip' """ - mime, encoding = mimetypes.guess_type(file_name, strict = False) + mime, encoding = mimetypes.guess_type(file_name, strict=False) if encoding: mime = 'archive/%s' % encoding if mime is None: @@ -113,7 +115,7 @@ class VersionedStorage(object): change history, using Mercurial repository as the storage method. """ - def __init__(self, path, charset = None): + def __init__(self, path, charset=None): """ Takes the path to the directory where the pages are to be kept. If the directory doen't exist, it will be created. If it's inside @@ -126,38 +128,38 @@ class VersionedStorage(object): if not os.path.exists(self.path): os.makedirs(self.path) self.repo_path = find_repo_path(self.path) - + self.ui = mercurial.ui.ui() self.ui.quiet = True self.ui._report_untrusted = False self.ui.setconfig('ui', 'interactive', False) - + if self.repo_path is None: self.repo_path = self.path create = True else: create = False - + self.repo_prefix = self.path[len(self.repo_path):].strip('/') self.repo = mercurial.hg.repository(self.ui, self.repo_path, - create = create) + create=create) def reopen(self): """Close and reopen the repo, to make sure we are up to date.""" self.repo = mercurial.hg.repository(self.ui, self.repo_path) def _file_path(self, title): - return os.path.join(self.path, urlquote(title, safe = '')) + return os.path.join(self.path, urlquote(title, safe='')) def _title_to_file(self, title): - return os.path.join(self.repo_prefix, urlquote(title, safe = '')) + return os.path.join(self.repo_prefix, urlquote(title, safe='')) def _file_to_title(self, filename): assert filename.startswith(self.repo_prefix) name = filename[len(self.repo_prefix):].strip('/') return urlunquote(name) - def __contains__(self, title): + def __contains__(self, title): return urlquote(title) in self.repo['tip'] def __iter__(self): @@ -195,40 +197,39 @@ class VersionedStorage(object): @with_working_copy_locked @with_storage_locked - def save_file(self, title, file_name, author = u'', comment = u'', parent = None): + def save_file(self, title, file_name, author=u'', comment=u'', parent=None): """Save an existing file as specified page.""" user = author.encode('utf-8') or u'anonymous'.encode('utf-8') text = comment.encode('utf-8') or u'comment'.encode('utf-8') - + repo_file = self._title_to_file(title) file_path = self._file_path(title) mercurial.util.rename(file_name, file_path) changectx = self._changectx() - + try: filectx_tip = changectx[repo_file] current_page_rev = filectx_tip.filerev() except mercurial.revlog.LookupError: self.repo.add([repo_file]) current_page_rev = -1 - + if parent is not None and current_page_rev != parent: msg = self.merge_changes(changectx, repo_file, text, user, parent) user = '' text = msg.encode('utf-8') - + self._commit([repo_file], text, user) - - + def save_data(self, title, data, **kwargs): """Save data as specified page.""" try: - temp_path = tempfile.mkdtemp(dir = self.path) + temp_path = tempfile.mkdtemp(dir=self.path) file_path = os.path.join(temp_path, 'saved') f = open(file_path, "wb") f.write(data) f.close() - self.save_file(title = title, file_name = file_path, **kwargs) + self.save_file(title=title, file_name=file_path, **kwargs) finally: try: os.unlink(file_path) @@ -240,14 +241,13 @@ class VersionedStorage(object): pass def save_text(self, text, **kwargs): - """Save text as specified page, encoded to charset.""" - self.save_data(data = text.encode(self.charset), **kwargs) - + """Save text as specified page, encoded to charset.""" + self.save_data(data=text.encode(self.charset), **kwargs) def _commit(self, files, text, user): match = mercurial.match.exact(self.repo_path, '', list(files)) - return self.repo.commit(match = match, text = text, user = user, force = True) - + return self.repo.commit(match=match, text=text, user=user, force=True) + def page_text(self, title): """Read unicode text of a page.""" data = self.open_page(title).read() @@ -260,7 +260,7 @@ class VersionedStorage(object): @with_working_copy_locked @with_storage_locked - def delete_page(self, title, author = u'', comment = u''): + def delete_page(self, title, author=u'', comment=u''): user = author.encode('utf-8') or 'anon' text = comment.encode('utf-8') or 'deleted' repo_file = self._title_to_file(title) @@ -276,11 +276,11 @@ class VersionedStorage(object): def open_page(self, title): if title not in self: raise DocumentNotFound() - + path = self._title_to_file(title) - logger.debug("Opening page %s", path) + logger.debug("Opening page %s", path) try: - return self.repo.wfile(path, 'rb') + return self.repo.wfile(path, 'rb') except IOError: logger.exception("Failed to open page %s", title) raise DocumentNotFound() @@ -294,7 +294,7 @@ class VersionedStorage(object): except OSError: return 0, 0, 0 return st_ino, st_size, st_mtime - + @with_working_copy_locked def page_meta(self, title): """Get page's revision, date, last editor and his edit comment.""" @@ -314,7 +314,7 @@ class VersionedStorage(object): def repo_revision(self): return self.repo['tip'].rev() - + def _changectx(self): return self.repo['tip'] @@ -325,7 +325,7 @@ class VersionedStorage(object): """ return guess_mime(self._file_path(title)) - def _find_filectx(self, title, rev = None): + def _find_filectx(self, title, rev=None): """Find the last revision in which the file existed.""" repo_file = self._title_to_file(title) @@ -338,9 +338,9 @@ class VersionedStorage(object): for parent in changectx.parents(): if parent != changectx: stack.append(parent) - + try: - fctx = changectx[repo_file] + fctx = changectx[repo_file] return fctx if rev is None else fctx.filectx(rev) except IndexError, LookupError: raise DocumentNotFound() @@ -349,7 +349,7 @@ class VersionedStorage(object): """Iterate over the page's history.""" filectx_tip = self._find_filectx(title) - + maxrev = filectx_tip.filerev() minrev = 0 for rev in range(maxrev, minrev - 1, -1): @@ -359,11 +359,11 @@ class VersionedStorage(object): 'replace').split('<')[0].strip() comment = unicode(filectx.description(), "utf-8", 'replace') tags = [t.rsplit('#', 1)[-1] for t in filectx.changectx().tags() if '#' in t] - + yield { - "version": rev, - "date": date, - "author": author, + "version": rev, + "date": date, + "author": author, "description": comment, "tag": tags, } @@ -376,19 +376,19 @@ class VersionedStorage(object): data = self.page_revision(title, rev) text = unicode(data, self.charset, 'replace') return text - + @with_working_copy_locked - def add_page_tag(self, title, rev, tag, user = "", doctag = True): + def add_page_tag(self, title, rev, tag, user="", doctag=True): if doctag: tag = "{title}#{tag}".format(**locals()) - + message = "Assigned tag {tag} to version {rev} of {title}".format(**locals()) - + fctx = self._find_filectx(title, rev) self.repo.tag( - names = tag, node = fctx.node(), local = False, - user = user, message = message, date = None - ) + names=tag, node=fctx.node(), local=False, + user=user, message=message, date=None, + ) def history(self): """Iterate over the history of entire wiki.""" @@ -414,7 +414,7 @@ class VersionedStorage(object): def all_pages(self): tip = self.repo['tip'] """Iterate over the titles of all pages in the wiki.""" - return [ urlunquote(filename) for filename in tip ] + return [urlunquote(filename) for filename in tip] def changed_since(self, rev): """Return all pages that changed since specified repository revision.""" diff --git a/lib/wlapi.py b/lib/wlapi.py index cc735963..ba1be146 100644 --- a/lib/wlapi.py +++ b/lib/wlapi.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # """ Abstraction over API for wolnelektury.pl @@ -12,64 +12,63 @@ import django.utils.simplejson as json import logging logger = logging.getLogger("fnp.lib.wlapi") + class APICallException(Exception): - pass + pass + def api_call(path, format="json"): def wrapper(func): - + @functools.wraps(func) - def wrapped(self, *args, **kwargs): + def wrapped(self, *args, **kwargs): generator = func(self, *args, **kwargs) - + data = generator.next() - + # prepare request rq = urllib2.Request(self.base_url + path + ".json") - + # will send POST when there is data, GET otherwise if data is not None: - rq.add_data(json.dumps(data)) + rq.add_data(json.dumps(data)) rq.add_header("Content-Type", "application/json") - + try: anwser = json.load(self.opener.open(rq)) return generator.send(anwser) except StopIteration: # by default, just return the anwser as a shorthand - return anwser + return anwser except urllib2.HTTPError, error: - return self._http_error(error) + return self._http_error(error) except Exception, error: - return self._error(error) + return self._error(error) return wrapped - + return wrapper - - + class WLAPI(object): - - def __init__(self, config_dict): + + def __init__(self, config_dict): self.base_url = config_dict['URL'] self.auth_realm = config_dict['AUTH_REALM'] - self.auth_user = config_dict['AUTH_USER'] - - auth_handler = urllib2.HTTPDigestAuthHandler(); + self.auth_user = config_dict['AUTH_USER'] + + auth_handler = urllib2.HTTPDigestAuthHandler() auth_handler.add_password( realm=self.auth_realm, uri=self.base_url, user=self.auth_user, passwd=config_dict['AUTH_PASSWD']) - + self.opener = urllib2.build_opener(auth_handler) - + def _http_error(self, error): return self._error() - + def _error(self, error): - raise APICallException(cause = error) - + raise APICallException(cause=error) + @api_call("books") def publish_book(self, document): - yield {"text": document.text, "compressed": False} - - \ No newline at end of file + yield {"text": document.text, "compressed": False} diff --git a/platforma/context_processors.py b/platforma/context_processors.py index eff3032c..db6c98b4 100644 --- a/platforma/context_processors.py +++ b/platforma/context_processors.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -__author__="lreqc" -__date__ ="$2009-09-03 08:34:10$" + def settings(request): from django.conf import settings - return {'MEDIA_URL': settings.MEDIA_URL, + return {'MEDIA_URL': settings.MEDIA_URL, 'STATIC_URL': settings.STATIC_URL, - 'REDMINE_URL': settings.REDMINE_URL } - - + 'REDMINE_URL': settings.REDMINE_URL} diff --git a/platforma/manage.py b/platforma/manage.py index 8e180f0e..1fc25dde 100755 --- a/platforma/manage.py +++ b/platforma/manage.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from django.core.management import execute_manager try: - import settings # Assumed to be in the same directory. + import settings # Assumed to be in the same directory. except ImportError: import sys sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) @@ -11,8 +11,8 @@ if __name__ == "__main__": # Append lib and apps directories to PYTHONPATH from os import path import sys - + PROJECT_ROOT = path.realpath(path.dirname(__file__)) sys.path += [PROJECT_ROOT + '/../apps', PROJECT_ROOT + '/../lib'] - + execute_manager(settings) diff --git a/platforma/settings.py b/platforma/settings.py index 88141489..90ecd27d 100644 --- a/platforma/settings.py +++ b/platforma/settings.py @@ -84,7 +84,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django_cas.middleware.CASMiddleware', - 'django.middleware.doc.XViewMiddleware', + 'django.middleware.doc.XViewMiddleware', 'maintenancemode.middleware.MaintenanceModeMiddleware', # 'debug_toolbar.middleware.DebugToolbarMiddleware' # ) @@ -104,7 +104,7 @@ TEMPLATE_DIRS = ( # # Central Auth System # -## Set this to where the CAS server lives +## Set this to where the CAS server lives # CAS_SERVER_URL = "http://cas.fnp.pl/ CAS_ADMIN_PREFIX = "/admin/" CAS_LOGOUT_COMPLETELY = True @@ -114,10 +114,10 @@ COMPRESS_CSS = { 'detail': { 'source_filenames': ( 'css/master.css', - 'css/gallery.css', - 'css/history.css', + 'css/gallery.css', + 'css/history.css', 'css/summary.css', - 'css/html.css', + 'css/html.css', 'css/jquery.autocomplete.css', 'css/dialogs.css', ), @@ -125,53 +125,53 @@ COMPRESS_CSS = { }, 'listing': { 'source_filenames': ( - 'css/filelist.css', - ), + 'css/filelist.css', + ), 'output_filename': 'compressed/listing_styles_?.css', } } - + COMPRESS_JS = { # everything except codemirror 'detail': { 'source_filenames': ( - 'js/jquery-1.4.2.min.js', - 'js/jquery.autocomplete.js', + 'js/jquery-1.4.2.min.js', + 'js/jquery.autocomplete.js', 'js/jquery.blockui.js', - 'js/jquery.elastic.js', + 'js/jquery.elastic.js', 'js/button_scripts.js', 'js/slugify.js', - + # wiki scripts 'js/wiki/wikiapi.js', 'js/wiki/base.js', 'js/wiki/xslt.js', - + # dialogs 'js/wiki/save_dialog.js', - + # views 'js/wiki/history_view.js', 'js/wiki/summary_view.js', 'js/wiki/source_editor.js', 'js/wiki/wysiwyg_editor.js', - 'js/wiki/scan_gallery.js', + 'js/wiki/scan_gallery.js', 'js/wiki/diff_view.js', - + # bootstrap 'js/wiki/main.js', - ), + ), 'output_filename': 'compressed/detail_scripts_?.js', }, 'listing': { 'source_filenames': ( - 'js/jquery-1.4.2.min.js', - 'js/slugify.js', - ), + 'js/jquery-1.4.2.min.js', + 'js/slugify.js', + ), 'output_filename': 'compressed/listing_scripts_?.js', } } - + COMPRESS = True COMPRESS_CSS_FILTERS = None COMPRESS_JS_FILTERS = None @@ -190,13 +190,13 @@ INSTALLED_APPS = ( 'django_nose', 'debug_toolbar', - + 'compress', 'south', 'sorl.thumbnail', 'filebrowser', - 'wiki', + 'wiki', 'toolbar', ) @@ -212,7 +212,7 @@ NOSE_ARGS = ( '--cover-package=' + ','.join(TEST_MODULES), '-d', '--with-coverage', - '--with-doctest' + '--with-doctest', ) @@ -232,14 +232,14 @@ except ImportError: pass try: - LOGGING_CONFIG_FILE + LOGGING_CONFIG_FILE except NameError: - LOGGING_CONFIG_FILE = os.path.join(PROJECT_ROOT, 'config', + LOGGING_CONFIG_FILE = os.path.join(PROJECT_ROOT, 'config', ('logging.cfg' if not DEBUG else 'logging.cfg.dev')) try: import logging import logging.config - - logging.config.fileConfig(LOGGING_CONFIG_FILE) + + logging.config.fileConfig(LOGGING_CONFIG_FILE) except ImportError, exc: - raise \ No newline at end of file + raise diff --git a/platforma/urls.py b/platforma/urls.py index c31e1459..d875a6ea 100644 --- a/platforma/urls.py +++ b/platforma/urls.py @@ -8,8 +8,8 @@ admin.autodiscover() urlpatterns = patterns('', # Auth - url(r'^accounts/login/$', 'django_cas.views.login', name = 'login'), - url(r'^accounts/logout/$', 'django_cas.views.logout', name = 'logout'), + url(r'^accounts/login/$', 'django_cas.views.login', name='login'), + url(r'^accounts/logout/$', 'django_cas.views.logout', name='logout'), # Admin panel (r'^admin/filebrowser/', include('filebrowser.urls')), diff --git a/scripts/crop.py b/scripts/crop.py index 3266e090..764e8e5c 100644 --- a/scripts/crop.py +++ b/scripts/crop.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import sys import os @@ -20,13 +20,12 @@ def crop(image, top=0, right=0, bottom=0, left=0): bottom = int(height * bottom) if left < 1: left = int(width * left) - + bounds = (int(left), int(top), int(width - right), int(height - bottom)) image = image.crop(bounds) image.load() return image - output_dir = realpath(os.getcwd()) + '/output' bounds = [float(i) for i in sys.argv[1].split(':')] @@ -37,6 +36,6 @@ for file_name in sys.argv[2:]: except IOError, e: sys.stderr.write('\nerror:%s:%s\n' % (file_name, e.message)) continue - + image = crop(image, *bounds) - image.save(output_dir + '/' + basename(file_name)) \ No newline at end of file + image.save(output_dir + '/' + basename(file_name)) diff --git a/scripts/imgconv.py b/scripts/imgconv.py index 3a7cfa9c..de2354e9 100644 --- a/scripts/imgconv.py +++ b/scripts/imgconv.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # This file is part of FNP-Redakcja, licensed under GNU Affero GPLv3 or later. -# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. +# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # import sys import os @@ -36,7 +36,7 @@ def ratio(image): """Return width to height ratio of image.""" width, height = image.size return float(width) / height - + def try_creating(directory): try: @@ -61,16 +61,16 @@ for file_name in sys.argv[1:]: except IOError, e: sys.stderr.write('\nerror:%s:%s\n' % (file_name, e.message)) continue - + # Check ratio if ratio(image) > 1: images = [crop(image, 0.6), crop(image, 0.6, from_right=True)] else: images = [image] - + for i, image in enumerate(images): image_name = '%s.%d.png' % (basename(base_name), i) - + # Save files small_image = resize(image, 640, 960) small_image = small_image.convert('L') @@ -83,7 +83,7 @@ for file_name in sys.argv[1:]: tmp_output_dir + '/' + image_name, )) os.remove(tmp_output_dir + '/' + image_name) - + # big_image = resize(image, 960, 1440) # big_image = big_image.convert('L') # big_image = big_image.filter(ImageFilter.SHARPEN) @@ -93,7 +93,7 @@ for file_name in sys.argv[1:]: # tmp_output_dir + '/' + image_name, # )) # os.remove(tmp_output_dir + '/' + image_name) - + sys.stderr.write('.') shutil.rmtree(tmp_output_dir) diff --git a/setup.py b/setup.py index 9c658b71..199329ec 100644 --- a/setup.py +++ b/setup.py @@ -4,18 +4,20 @@ import os from distutils.core import setup +RESOURCE_PATHS = ('templates', 'static', 'media', 'locale', 'config') + + def is_package(path): - return ( - os.path.isdir(path) and - os.path.isfile(os.path.join(path, '__init__.py')) - ) + return os.path.isdir(path) \ + and os.path.isfile(os.path.join(path, '__init__.py')) -def find_packages(path, base="" ): + +def find_packages(path, base=""): """ Find all packages in path """ packages = {} for item in os.listdir(path): dir = os.path.join(path, item) - if is_package( dir ): + if is_package(dir): if base: module_name = "%(base)s.%(item)s" % vars() else: @@ -24,50 +26,53 @@ def find_packages(path, base="" ): packages.update(find_packages(dir, module_name)) return packages + def files_from(*paths, **kwargs): - base = kwargs.pop('base') + base = kwargs.pop('base') + def generator(): for path in paths: - path = os.path.join(base, path) - if not os.path.isdir(path) or is_package(path): continue - - for dir, _, files in os.walk(path): - for file in files: - yield os.path.relpath(os.path.join(dir, file),base) - + path = os.path.join(base, path) + if not os.path.isdir(path) or is_package(path): + continue + + for dir, _, files in os.walk(path): + for file in files: + yield os.path.relpath(os.path.join(dir, file), base) + return list(generator()) -RESOURCE_PATHS = ('templates', 'static', 'media', 'locale', 'config') -def django_setup(project, apps=[], apps_dir='apps', package_dir = {}, packages = [], package_data = {}, **kwargs): - +def django_setup(project, apps=[], apps_dir='apps', package_dir={}, packages=[], package_data={}, **kwargs): + # directories - extra_dirs = dict( (app, os.path.join(apps_dir,app)) for app in apps ) + extra_dirs = dict((app, os.path.join(apps_dir, app)) for app in apps) extra_dirs[project] = project package_dir.update(extra_dirs) - + # applications packages.extend(apps) - # with all subpackages + # with all subpackages for app in apps: packages.extend(find_packages(os.path.join(apps_dir, app), base=app)) # and the project packages.append(project) - - # extra data + + # extra data extra_data = {} for app in apps: - extra_data[app] = files_from(*RESOURCE_PATHS, base=os.path.join(apps_dir, app)) + extra_data[app] = files_from(*RESOURCE_PATHS, base=os.path.join(apps_dir, app)) extra_data[project] = files_from(*RESOURCE_PATHS, base=project) - package_data.update(extra_data) - + package_data.update(extra_data) + return setup( - package_dir = package_dir, - packages = packages, - package_data = package_data, **kwargs) - + package_dir=package_dir, + packages=packages, + package_data=package_data, **kwargs) + + # -# The reald stuff :) +# The real stuff :) # django_setup( name='fnp-redakcja', @@ -76,31 +81,29 @@ django_setup( author="Fundacja Nowoczesna Polska", author_email='fundacja@nowoczesnapolska.org.pl', license="GNU Affero General Public License 3", - maintainer='Łukasz Rekucki', + maintainer='Łukasz Rekucki', maintainer_email='lrekucki@gmail.com', url='http://github.com/fnp/redakcja', - package_dir = {'': 'lib'}, - py_modules = [ + package_dir={'': 'lib'}, + py_modules=[ 'wlapi', - 'vstorage', - ], + 'vstorage', + ], scripts=[ - 'scripts/crop.py', + 'scripts/crop.py', 'scripts/imgconv.py', - ], + ], # django applications - project = 'platforma', - apps_dir = 'apps', - apps = [ + project='platforma', + apps_dir='apps', + apps=[ 'compress', - 'django_cas', + 'django_cas', 'filebrowser', 'toolbar', - 'wiki', + 'wiki', ], - requires = [ + requires=[ "librarian (>=1.3)", - "Django (>=1.1.1,<1.2)", - ] - # data_files=[ ('', ['LICENSE', 'NOTICE', 'README.rst', 'AUTHORS.md', 'requirements.txt'])], -) + "Django (>=1.1.1,<1.2)", + ]) -- 2.20.1