From 23e063b67b3b8edb45cb967103dd9159ce79189f Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Wed, 13 Nov 2013 14:09:51 +1100 Subject: [PATCH 001/654] Make {% feincms_breadcrumbs %} isinstance check BasePage not Page. See #487 --- feincms/module/page/templatetags/feincms_page_tags.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/module/page/templatetags/feincms_page_tags.py b/feincms/module/page/templatetags/feincms_page_tags.py index 3b1f70365..7c336a5d8 100644 --- a/feincms/module/page/templatetags/feincms_page_tags.py +++ b/feincms/module/page/templatetags/feincms_page_tags.py @@ -10,7 +10,7 @@ from django.conf import settings from django.http import HttpRequest -from feincms.module.page.models import Page +from feincms.module.page.models import BasePage, Page from feincms.utils.templatetags import (SimpleNodeWithVarAndArgs, do_simple_node_with_var_and_args_helper, SimpleAssignmentNodeWithVarAndArgs, @@ -306,7 +306,7 @@ def feincms_breadcrumbs(page, include_self=True): {% feincms_breadcrumbs feincms_page %} """ - if not page or not isinstance(page, Page): + if not page or not isinstance(page, BasePage): raise ValueError("feincms_breadcrumbs must be called with a valid Page object") ancs = page.get_ancestors() From 73e692ba0e2e5570b3ca3b7c9fae87fb23489b8e Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 4 Feb 2014 09:13:02 +0100 Subject: [PATCH 002/654] Fix a typo in a (private) method name --- feincms/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/feincms/models.py b/feincms/models.py index 9286047cc..b3b1eaaaa 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -194,7 +194,7 @@ def _fetch_content_type_count_helper(self, pk, regions=None): return _c - def _popuplate_content_type_caches(self, types): + def _populate_content_type_caches(self, types): """ Populate internal caches for all content types passed """ @@ -230,7 +230,7 @@ def _fetch_regions(self): """ if 'regions' not in self._cache: - self._popuplate_content_type_caches( + self._populate_content_type_caches( self.item._feincms_content_types) contents = {} for cls, content_list in self._cache['cts'].items(): @@ -260,7 +260,7 @@ def all_of_type(self, type_or_tuple): content_list = [] if not hasattr(type_or_tuple, '__iter__'): type_or_tuple = (type_or_tuple,) - self._popuplate_content_type_caches(type_or_tuple) + self._populate_content_type_caches(type_or_tuple) for type, contents in self._cache['cts'].items(): if any(issubclass(type, t) for t in type_or_tuple): From 7916cac924f77367c5af9da4d18ad8a5609eceb4 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 4 Feb 2014 09:16:53 +0100 Subject: [PATCH 003/654] Development branch --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 823e59932..a472b6c55 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 9, 3) +VERSION = (1, 10, 0, 'pre') __version__ = '.'.join(map(str, VERSION)) From 3bd189e1d6972edde8567ef3393720d1cefdeec0 Mon Sep 17 00:00:00 2001 From: Stefan Reinhard Date: Tue, 4 Feb 2014 15:20:22 +0100 Subject: [PATCH 004/654] Populate values for ApplicationContent admin_fields from parameters --- feincms/content/application/models.py | 2 ++ tests/testapp/models.py | 1 + tests/testapp/tests/test_page.py | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index 867a8cf4d..919eb1bbf 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -212,6 +212,8 @@ def __init__(self, *args, **kwargs): for k, v in self.custom_fields.items(): self.fields[k] = v + if k in self.instance.parameters: + self.fields[k].initial = self.instance.parameters[k] def save(self, commit=True, *args, **kwargs): # Django ModelForms return the model instance from save. We'll diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 23ef36d77..668202fc1 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -53,6 +53,7 @@ def get_admin_fields(form, *args, **kwargs): 'Exclude everything other than the application\'s content' ' when rendering subpages.'), ), + 'custom_field': forms.CharField(), } diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 91d3c77f7..7f3431df8 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1416,6 +1416,17 @@ def test_25_applicationcontent(self): self.assertContains( self.client.get('/admin/page/page/%d/' % page.id), 'exclusive_subpages') + self.assertContains( + self.client.get('/admin/page/page/%d/' % page.id), + 'custom_field' + ) + + # Check if admin_fields get populated correctly + app_ct = page.applicationcontent_set.all()[0] + app_ct.parameters = '{"custom_field":"val42", "exclusive_subpages": false}' + app_ct.save() + r = self.client.get('/admin/page/page/%d/' % page.id) + self.assertContains(r, 'val42') def test_26_page_form_initial(self): self.create_default_page_set() From 681d279ca291c8edfb82b02c3fd91c77f693d09a Mon Sep 17 00:00:00 2001 From: Stefan Reinhard Date: Tue, 4 Feb 2014 16:25:49 +0100 Subject: [PATCH 005/654] flake8 clean --- feincms/content/application/models.py | 3 +-- tests/testapp/tests/test_page.py | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index 919eb1bbf..867295e65 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -211,9 +211,8 @@ def __init__(self, *args, **kwargs): get_fields(self, *args, **kwargs)) for k, v in self.custom_fields.items(): + v.initial = self.instance.parameters.get(k) self.fields[k] = v - if k in self.instance.parameters: - self.fields[k].initial = self.instance.parameters[k] def save(self, commit=True, *args, **kwargs): # Django ModelForms return the model instance from save. We'll diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 7f3431df8..d18fe3228 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1423,7 +1423,8 @@ def test_25_applicationcontent(self): # Check if admin_fields get populated correctly app_ct = page.applicationcontent_set.all()[0] - app_ct.parameters = '{"custom_field":"val42", "exclusive_subpages": false}' + app_ct.parameters = \ + '{"custom_field":"val42", "exclusive_subpages": false}' app_ct.save() r = self.client.get('/admin/page/page/%d/' % page.id) self.assertContains(r, 'val42') From ca4aa5226e91e9e18ad294ed0346a060e0fe3c13 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Fri, 7 Feb 2014 11:00:15 +0100 Subject: [PATCH 006/654] Add a blurp about adding other rich text libraries to the docs, curtesy of Matthia's answer to a ticket. --- docs/contenttypes.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/contenttypes.rst b/docs/contenttypes.rst index 89c27bd04..4c6e08551 100644 --- a/docs/contenttypes.rst +++ b/docs/contenttypes.rst @@ -431,6 +431,25 @@ Additional arguments for :func:`~feincms.models.Base.create_content_type`: which are not explicitly whitelisted. The default is ``False``. +Other rich text libraries +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Other rich text widgets can be wired up for the RichTextContent. +You would have to write two functions: One which is called when +rich text editing functionality is added ("richify"), and another +one which is called when functionality is removed ("poorify"). +The second is necessary because rich text editors do not like +being dragged; when dragging a rich text content type, it is first +poorified and then richified again as soon as the content type has +been dropped into its final position. + +To perform those operations + * Add a function adding the new rich text editor to + ``contentblock_init_handlers`` and to ``contentblock_move_handlers.richify`` + * Add a function removing the rich text editor to + ``contentblock_move_handlers.poorify`` + + RSS feeds --------- .. module:: feincms.content.rss.models From a6ee8b89f09ea284ddc2cf5ee5512898ea90f622 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Fri, 7 Feb 2014 11:13:25 +0100 Subject: [PATCH 007/654] Update jquery to 1.11.0 --- feincms/static/feincms/jquery-1.11.0.min.js | 4 ++++ feincms/templates/admin/feincms/load-jquery.include | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 feincms/static/feincms/jquery-1.11.0.min.js diff --git a/feincms/static/feincms/jquery-1.11.0.min.js b/feincms/static/feincms/jquery-1.11.0.min.js new file mode 100644 index 000000000..73f33fb3a --- /dev/null +++ b/feincms/static/feincms/jquery-1.11.0.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k="".trim,l={},m="1.11.0",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(l.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:k&&!k.call("\ufeff\xa0")?function(a){return null==a?"":k.call(a)}:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||n.guid++,e):void 0},now:function(){return+new Date},support:l}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s="sizzle"+-new Date,t=a.document,u=0,v=0,w=eb(),x=eb(),y=eb(),z=function(a,b){return a===b&&(j=!0),0},A="undefined",B=1<<31,C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=D.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",M=L.replace("w","w#"),N="\\["+K+"*("+L+")"+K+"*(?:([*^$|!~]?=)"+K+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+M+")|)|)"+K+"*\\]",O=":("+L+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+N.replace(3,8)+")*)|.*)\\)|)",P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(O),U=new RegExp("^"+M+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L.replace("w","w*")+")"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=/'|\\/g,ab=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),bb=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{G.apply(D=H.call(t.childNodes),t.childNodes),D[t.childNodes.length].nodeType}catch(cb){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function db(a,b,d,e){var f,g,h,i,j,m,p,q,u,v;if((b?b.ownerDocument||b:t)!==l&&k(b),b=b||l,d=d||[],!a||"string"!=typeof a)return d;if(1!==(i=b.nodeType)&&9!==i)return[];if(n&&!e){if(f=Z.exec(a))if(h=f[1]){if(9===i){if(g=b.getElementById(h),!g||!g.parentNode)return d;if(g.id===h)return d.push(g),d}else if(b.ownerDocument&&(g=b.ownerDocument.getElementById(h))&&r(b,g)&&g.id===h)return d.push(g),d}else{if(f[2])return G.apply(d,b.getElementsByTagName(a)),d;if((h=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(h)),d}if(c.qsa&&(!o||!o.test(a))){if(q=p=s,u=b,v=9===i&&a,1===i&&"object"!==b.nodeName.toLowerCase()){m=ob(a),(p=b.getAttribute("id"))?q=p.replace(_,"\\$&"):b.setAttribute("id",q),q="[id='"+q+"'] ",j=m.length;while(j--)m[j]=q+pb(m[j]);u=$.test(a)&&mb(b.parentNode)||b,v=m.join(",")}if(v)try{return G.apply(d,u.querySelectorAll(v)),d}catch(w){}finally{p||b.removeAttribute("id")}}}return xb(a.replace(P,"$1"),b,d,e)}function eb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function fb(a){return a[s]=!0,a}function gb(a){var b=l.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function hb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function ib(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||B)-(~a.sourceIndex||B);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function jb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function kb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function lb(a){return fb(function(b){return b=+b,fb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function mb(a){return a&&typeof a.getElementsByTagName!==A&&a}c=db.support={},f=db.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},k=db.setDocument=function(a){var b,e=a?a.ownerDocument||a:t,g=e.defaultView;return e!==l&&9===e.nodeType&&e.documentElement?(l=e,m=e.documentElement,n=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){k()},!1):g.attachEvent&&g.attachEvent("onunload",function(){k()})),c.attributes=gb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=gb(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(e.getElementsByClassName)&&gb(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=gb(function(a){return m.appendChild(a).id=s,!e.getElementsByName||!e.getElementsByName(s).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==A&&n){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ab,bb);return function(a){var c=typeof a.getAttributeNode!==A&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==A?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==A&&n?b.getElementsByClassName(a):void 0},p=[],o=[],(c.qsa=Y.test(e.querySelectorAll))&&(gb(function(a){a.innerHTML="",a.querySelectorAll("[t^='']").length&&o.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||o.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll(":checked").length||o.push(":checked")}),gb(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&o.push("name"+K+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||o.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),o.push(",.*:")})),(c.matchesSelector=Y.test(q=m.webkitMatchesSelector||m.mozMatchesSelector||m.oMatchesSelector||m.msMatchesSelector))&&gb(function(a){c.disconnectedMatch=q.call(a,"div"),q.call(a,"[s!='']:x"),p.push("!=",O)}),o=o.length&&new RegExp(o.join("|")),p=p.length&&new RegExp(p.join("|")),b=Y.test(m.compareDocumentPosition),r=b||Y.test(m.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},z=b?function(a,b){if(a===b)return j=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===t&&r(t,a)?-1:b===e||b.ownerDocument===t&&r(t,b)?1:i?I.call(i,a)-I.call(i,b):0:4&d?-1:1)}:function(a,b){if(a===b)return j=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],k=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:i?I.call(i,a)-I.call(i,b):0;if(f===g)return ib(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)k.unshift(c);while(h[d]===k[d])d++;return d?ib(h[d],k[d]):h[d]===t?-1:k[d]===t?1:0},e):l},db.matches=function(a,b){return db(a,null,null,b)},db.matchesSelector=function(a,b){if((a.ownerDocument||a)!==l&&k(a),b=b.replace(S,"='$1']"),!(!c.matchesSelector||!n||p&&p.test(b)||o&&o.test(b)))try{var d=q.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return db(b,l,null,[a]).length>0},db.contains=function(a,b){return(a.ownerDocument||a)!==l&&k(a),r(a,b)},db.attr=function(a,b){(a.ownerDocument||a)!==l&&k(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!n):void 0;return void 0!==f?f:c.attributes||!n?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},db.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},db.uniqueSort=function(a){var b,d=[],e=0,f=0;if(j=!c.detectDuplicates,i=!c.sortStable&&a.slice(0),a.sort(z),j){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return i=null,a},e=db.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=db.selectors={cacheLength:50,createPseudo:fb,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ab,bb),a[3]=(a[4]||a[5]||"").replace(ab,bb),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||db.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&db.error(a[0]),a},PSEUDO:function(a){var b,c=!a[5]&&a[2];return V.CHILD.test(a[0])?null:(a[3]&&void 0!==a[4]?a[2]=a[4]:c&&T.test(c)&&(b=ob(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ab,bb).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=w[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&w(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==A&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=db.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),t=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&t){k=q[s]||(q[s]={}),j=k[a]||[],n=j[0]===u&&j[1],m=j[0]===u&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[u,n,m];break}}else if(t&&(j=(b[s]||(b[s]={}))[a])&&j[0]===u)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(t&&((l[s]||(l[s]={}))[a]=[u,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||db.error("unsupported pseudo: "+a);return e[s]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?fb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:fb(function(a){var b=[],c=[],d=g(a.replace(P,"$1"));return d[s]?fb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:fb(function(a){return function(b){return db(a,b).length>0}}),contains:fb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:fb(function(a){return U.test(a||"")||db.error("unsupported lang: "+a),a=a.replace(ab,bb).toLowerCase(),function(b){var c;do if(c=n?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===m},focus:function(a){return a===l.activeElement&&(!l.hasFocus||l.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:lb(function(){return[0]}),last:lb(function(a,b){return[b-1]}),eq:lb(function(a,b,c){return[0>c?c+b:c]}),even:lb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:lb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:lb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:lb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function qb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=v++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[u,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[s]||(b[s]={}),(h=i[d])&&h[0]===u&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function rb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function sb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function tb(a,b,c,d,e,f){return d&&!d[s]&&(d=tb(d)),e&&!e[s]&&(e=tb(e,f)),fb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||wb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:sb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=sb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=sb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ub(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],i=g||d.relative[" "],j=g?1:0,k=qb(function(a){return a===b},i,!0),l=qb(function(a){return I.call(b,a)>-1},i,!0),m=[function(a,c,d){return!g&&(d||c!==h)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>j;j++)if(c=d.relative[a[j].type])m=[qb(rb(m),c)];else{if(c=d.filter[a[j].type].apply(null,a[j].matches),c[s]){for(e=++j;f>e;e++)if(d.relative[a[e].type])break;return tb(j>1&&rb(m),j>1&&pb(a.slice(0,j-1).concat({value:" "===a[j-2].type?"*":""})).replace(P,"$1"),c,e>j&&ub(a.slice(j,e)),f>e&&ub(a=a.slice(e)),f>e&&pb(a))}m.push(c)}return rb(m)}function vb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,i,j,k){var m,n,o,p=0,q="0",r=f&&[],s=[],t=h,v=f||e&&d.find.TAG("*",k),w=u+=null==t?1:Math.random()||.1,x=v.length;for(k&&(h=g!==l&&g);q!==x&&null!=(m=v[q]);q++){if(e&&m){n=0;while(o=a[n++])if(o(m,g,i)){j.push(m);break}k&&(u=w)}c&&((m=!o&&m)&&p--,f&&r.push(m))}if(p+=q,c&&q!==p){n=0;while(o=b[n++])o(r,s,g,i);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=E.call(j));s=sb(s)}G.apply(j,s),k&&!f&&s.length>0&&p+b.length>1&&db.uniqueSort(j)}return k&&(u=w,h=t),r};return c?fb(f):f}g=db.compile=function(a,b){var c,d=[],e=[],f=y[a+" "];if(!f){b||(b=ob(a)),c=b.length;while(c--)f=ub(b[c]),f[s]?d.push(f):e.push(f);f=y(a,vb(e,d))}return f};function wb(a,b,c){for(var d=0,e=b.length;e>d;d++)db(a,b[d],c);return c}function xb(a,b,e,f){var h,i,j,k,l,m=ob(a);if(!f&&1===m.length){if(i=m[0]=m[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&c.getById&&9===b.nodeType&&n&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(ab,bb),b)||[])[0],!b)return e;a=a.slice(i.shift().value.length)}h=V.needsContext.test(a)?0:i.length;while(h--){if(j=i[h],d.relative[k=j.type])break;if((l=d.find[k])&&(f=l(j.matches[0].replace(ab,bb),$.test(i[0].type)&&mb(b.parentNode)||b))){if(i.splice(h,1),a=f.length&&pb(i),!a)return G.apply(e,f),e;break}}}return g(a,m)(f,b,!n,e,$.test(a)&&mb(b.parentNode)||b),e}return c.sortStable=s.split("").sort(z).join("")===s,c.detectDuplicates=!!j,k(),c.sortDetached=gb(function(a){return 1&a.compareDocumentPosition(l.createElement("div"))}),gb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||hb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&gb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||hb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),gb(function(a){return null==a.getAttribute("disabled")})||hb(J,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),db}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=a.document,A=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,B=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:A.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:z,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=z.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return y.find(a);this.length=1,this[0]=d}return this.context=z,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};B.prototype=n.fn,y=n(z);var C=/^(?:parents|prev(?:Until|All))/,D={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!n(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function E(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return E(a,"nextSibling")},prev:function(a){return E(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(D[a]||(e=n.unique(e)),C.test(a)&&(e=e.reverse())),this.pushStack(e)}});var F=/\S+/g,G={};function H(a){var b=G[a]={};return n.each(a.match(F)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?G[a]||H(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&n.each(arguments,function(a,c){var d;while((d=n.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){if(a===!0?!--n.readyWait:!n.isReady){if(!z.body)return setTimeout(n.ready);n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(z,[n]),n.fn.trigger&&n(z).trigger("ready").off("ready"))}}});function J(){z.addEventListener?(z.removeEventListener("DOMContentLoaded",K,!1),a.removeEventListener("load",K,!1)):(z.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(z.addEventListener||"load"===event.type||"complete"===z.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===z.readyState)setTimeout(n.ready);else if(z.addEventListener)z.addEventListener("DOMContentLoaded",K,!1),a.addEventListener("load",K,!1);else{z.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&z.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!n.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}J(),n.ready()}}()}return I.promise(b)};var L="undefined",M;for(M in n(l))break;l.ownLast="0"!==M,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c=z.getElementsByTagName("body")[0];c&&(a=z.createElement("div"),a.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",b=z.createElement("div"),c.appendChild(a).appendChild(b),typeof b.style.zoom!==L&&(b.style.cssText="border:0;margin:0;width:1px;padding:1px;display:inline;zoom:1",(l.inlineBlockNeedsLayout=3===b.offsetWidth)&&(c.style.zoom=1)),c.removeChild(a),a=b=null)}),function(){var a=z.createElement("div");if(null==l.deleteExpando){l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}}a=null}(),n.acceptData=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(n.acceptData(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f +}}function S(a,b,c){if(n.acceptData(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d]));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},X=/^(?:checkbox|radio)$/i;!function(){var a=z.createDocumentFragment(),b=z.createElement("div"),c=z.createElement("input");if(b.setAttribute("className","t"),b.innerHTML="
a",l.leadingWhitespace=3===b.firstChild.nodeType,l.tbody=!b.getElementsByTagName("tbody").length,l.htmlSerialize=!!b.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==z.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,a.appendChild(c),l.appendChecked=c.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,a.appendChild(b),b.innerHTML="",l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){l.noCloneEvent=!1}),b.cloneNode(!0).click()),null==l.deleteExpando){l.deleteExpando=!0;try{delete b.test}catch(d){l.deleteExpando=!1}}a=b=c=null}(),function(){var b,c,d=z.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),l[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var Y=/^(?:input|select|textarea)$/i,Z=/^key/,$=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,ab=/^([^.]*)(?:\.(.+)|)$/;function bb(){return!0}function cb(){return!1}function db(){try{return z.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof n===L||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(F)||[""],h=b.length;while(h--)f=ab.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(F)||[""],j=b.length;while(j--)if(h=ab.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,m,o=[d||z],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||z,3!==d.nodeType&&8!==d.nodeType&&!_.test(p+n.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[n.expando]?b:new n.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),k=n.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!n.isWindow(d)){for(i=k.delegateType||p,_.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||z)&&o.push(l.defaultView||l.parentWindow||a)}m=0;while((h=o[m++])&&!b.isPropagationStopped())b.type=m>1?i:k.bindType||p,f=(n._data(h,"events")||{})[b.type]&&n._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&n.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&n.acceptData(d)&&g&&d[p]&&!n.isWindow(d)){l=d[g],l&&(d[g]=null),n.event.triggered=p;try{d[p]()}catch(r){}n.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((n.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?n(c,this).index(i)>=0:n.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ib=/^\s+/,jb=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,kb=/<([\w:]+)/,lb=/\s*$/g,sb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]},tb=eb(z),ub=tb.appendChild(z.createElement("div"));sb.optgroup=sb.option,sb.tbody=sb.tfoot=sb.colgroup=sb.caption=sb.thead,sb.th=sb.td;function vb(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==L?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==L?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,vb(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function wb(a){X.test(a.type)&&(a.defaultChecked=a.checked)}function xb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function yb(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function zb(a){var b=qb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ab(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}function Bb(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Cb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(yb(b).text=a.text,zb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&X.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}n.extend({clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!hb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ub.innerHTML=a.outerHTML,ub.removeChild(f=ub.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=vb(f),h=vb(a),g=0;null!=(e=h[g]);++g)d[g]&&Cb(e,d[g]);if(b)if(c)for(h=h||vb(a),d=d||vb(f),g=0;null!=(e=h[g]);g++)Bb(e,d[g]);else Bb(a,f);return d=vb(f,"script"),d.length>0&&Ab(d,!i&&vb(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k,m=a.length,o=eb(b),p=[],q=0;m>q;q++)if(f=a[q],f||0===f)if("object"===n.type(f))n.merge(p,f.nodeType?[f]:f);else if(mb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(kb.exec(f)||["",""])[1].toLowerCase(),k=sb[i]||sb._default,h.innerHTML=k[1]+f.replace(jb,"<$1>")+k[2],e=k[0];while(e--)h=h.lastChild;if(!l.leadingWhitespace&&ib.test(f)&&p.push(b.createTextNode(ib.exec(f)[0])),!l.tbody){f="table"!==i||lb.test(f)?""!==k[1]||lb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)n.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}n.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),l.appendChecked||n.grep(vb(p,"input"),wb),q=0;while(f=p[q++])if((!d||-1===n.inArray(f,d))&&(g=n.contains(f.ownerDocument,f),h=vb(o.appendChild(f),"script"),g&&Ab(h),c)){e=0;while(f=h[e++])pb.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.deleteExpando,m=n.event.special;null!=(d=a[h]);h++)if((b||n.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k?delete d[i]:typeof d.removeAttribute!==L?d.removeAttribute(i):d[i]=null,c.push(f))}}}),n.fn.extend({text:function(a){return W(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||z).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=xb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(vb(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&Ab(vb(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(vb(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return W(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(gb,""):void 0;if(!("string"!=typeof a||nb.test(a)||!l.htmlSerialize&&hb.test(a)||!l.leadingWhitespace&&ib.test(a)||sb[(kb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(jb,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(vb(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(vb(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,k=this.length,m=this,o=k-1,p=a[0],q=n.isFunction(p);if(q||k>1&&"string"==typeof p&&!l.checkClone&&ob.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(k&&(i=n.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=n.map(vb(i,"script"),yb),f=g.length;k>j;j++)d=i,j!==o&&(d=n.clone(d,!0,!0),f&&n.merge(g,vb(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,n.map(g,zb),j=0;f>j;j++)d=g[j],pb.test(d.type||"")&&!n._data(d,"globalEval")&&n.contains(h,d)&&(d.src?n._evalUrl&&n._evalUrl(d.src):n.globalEval((d.text||d.textContent||d.innerHTML||"").replace(rb,"")));i=c=null}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],g=n(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Db,Eb={};function Fb(b,c){var d=n(c.createElement(b)).appendTo(c.body),e=a.getDefaultComputedStyle?a.getDefaultComputedStyle(d[0]).display:n.css(d[0],"display");return d.detach(),e}function Gb(a){var b=z,c=Eb[a];return c||(c=Fb(a,b),"none"!==c&&c||(Db=(Db||n(" diff --git a/feincms/templates/content/video/youtube.html b/feincms/templates/content/video/youtube.html deleted file mode 100644 index 2b5fec51c..000000000 --- a/feincms/templates/content/video/youtube.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - From d6fcfe6218f82f50ccafbcaae53537350baa2082 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:06:14 +0200 Subject: [PATCH 282/654] ckeditor: Reindent template, less global vars --- .../admin/content/richtext/init_ckeditor.html | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/feincms/templates/admin/content/richtext/init_ckeditor.html b/feincms/templates/admin/content/richtext/init_ckeditor.html index 61559e3d7..4c46b7dc6 100644 --- a/feincms/templates/admin/content/richtext/init_ckeditor.html +++ b/feincms/templates/admin/content/richtext/init_ckeditor.html @@ -6,16 +6,29 @@ From 314e33cd43b71fee921bcd4068351aead7196af8 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:06:52 +0200 Subject: [PATCH 283/654] Move the MediaFileContent into the medialibrary app ... thereby making it even clearer that it is not the way to go anymore. --- feincms/contents.py | 66 ---------------- .../commands/medialibrary_to_filer.py | 5 +- feincms/module/medialibrary/contents.py | 75 +++++++++++++++++++ tests/testapp/models.py | 3 +- tests/testapp/tests/test_cms.py | 3 +- 5 files changed, 81 insertions(+), 71 deletions(-) create mode 100644 feincms/module/medialibrary/contents.py diff --git a/feincms/contents.py b/feincms/contents.py index 7414d797b..76f4253d9 100644 --- a/feincms/contents.py +++ b/feincms/contents.py @@ -10,72 +10,6 @@ from feincms import settings from feincms.admin.item_editor import FeinCMSInline from feincms.contrib.richtext import RichTextField -from feincms.module.medialibrary.fields import ContentWithMediaFile - - -class MediaFileContentInline(FeinCMSInline): - raw_id_fields = ('mediafile',) - radio_fields = {'type': admin.VERTICAL} - - -class MediaFileContent(ContentWithMediaFile): - """ - Rehashed, backwards-incompatible media file content which does not contain - the problems from v1 anymore. - - Create a media file content as follows:: - - from feincms.content.medialibrary.v2 import MediaFileContent - Page.create_content_type(MediaFileContent, TYPE_CHOICES=( - ('default', _('Default')), - ('lightbox', _('Lightbox')), - ('whatever', _('Whatever')), - )) - - For a media file of type 'image' and type 'lightbox', the following - templates are tried in order: - - * content/mediafile/image_lightbox.html - * content/mediafile/image.html - * content/mediafile/lightbox.html - * content/mediafile/default.html - - The context contains ``content`` and ``request`` (if available). - """ - - feincms_item_editor_inline = MediaFileContentInline - - class Meta: - abstract = True - verbose_name = _('media file') - verbose_name_plural = _('media files') - - @classmethod - def initialize_type(cls, TYPE_CHOICES=None): - if TYPE_CHOICES is None: - raise ImproperlyConfigured( - 'You have to set TYPE_CHOICES when' - ' creating a %s' % cls.__name__) - - cls.add_to_class( - 'type', - models.CharField( - _('type'), - max_length=20, - choices=TYPE_CHOICES, - default=TYPE_CHOICES[0][0], - ) - ) - - def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string([ - 'content/mediafile/%s_%s.html' % (self.mediafile.type, self.type), - 'content/mediafile/%s.html' % self.mediafile.type, - 'content/mediafile/%s.html' % self.type, - 'content/mediafile/default.html', - ], ctx, context_instance=kwargs.get('context')) class RawContent(models.Model): diff --git a/feincms/management/commands/medialibrary_to_filer.py b/feincms/management/commands/medialibrary_to_filer.py index 2c52c70c2..ff34c8d41 100644 --- a/feincms/management/commands/medialibrary_to_filer.py +++ b/feincms/management/commands/medialibrary_to_filer.py @@ -4,9 +4,8 @@ from django.core.management.base import NoArgsCommand from django.contrib.auth.models import User -from feincms.contents import ( - MediaFileContent, FilerFileContent, FilerImageContent, -) +from feincms.contents import FilerFileContent, FilerImageContent +from feincms.module.medialibrary.contents import MediaFileContent from feincms.module.medialibrary.models import MediaFile from feincms.module.page.models import Page diff --git a/feincms/module/medialibrary/contents.py b/feincms/module/medialibrary/contents.py new file mode 100644 index 000000000..c87cebe65 --- /dev/null +++ b/feincms/module/medialibrary/contents.py @@ -0,0 +1,75 @@ +from __future__ import unicode_literals + +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured +from django.db import models +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ + +from feincms.admin.item_editor import FeinCMSInline +from feincms.module.medialibrary.fields import ContentWithMediaFile + + +class MediaFileContentInline(FeinCMSInline): + raw_id_fields = ('mediafile',) + radio_fields = {'type': admin.VERTICAL} + + +class MediaFileContent(ContentWithMediaFile): + """ + Rehashed, backwards-incompatible media file content which does not contain + the problems from v1 anymore. + + Create a media file content as follows:: + + from feincms.content.medialibrary.v2 import MediaFileContent + Page.create_content_type(MediaFileContent, TYPE_CHOICES=( + ('default', _('Default')), + ('lightbox', _('Lightbox')), + ('whatever', _('Whatever')), + )) + + For a media file of type 'image' and type 'lightbox', the following + templates are tried in order: + + * content/mediafile/image_lightbox.html + * content/mediafile/image.html + * content/mediafile/lightbox.html + * content/mediafile/default.html + + The context contains ``content`` and ``request`` (if available). + """ + + feincms_item_editor_inline = MediaFileContentInline + + class Meta: + abstract = True + verbose_name = _('media file') + verbose_name_plural = _('media files') + + @classmethod + def initialize_type(cls, TYPE_CHOICES=None): + if TYPE_CHOICES is None: + raise ImproperlyConfigured( + 'You have to set TYPE_CHOICES when' + ' creating a %s' % cls.__name__) + + cls.add_to_class( + 'type', + models.CharField( + _('type'), + max_length=20, + choices=TYPE_CHOICES, + default=TYPE_CHOICES[0][0], + ) + ) + + def render(self, **kwargs): + ctx = {'content': self} + ctx.update(kwargs) + return render_to_string([ + 'content/mediafile/%s_%s.html' % (self.mediafile.type, self.type), + 'content/mediafile/%s.html' % self.mediafile.type, + 'content/mediafile/%s.html' % self.type, + 'content/mediafile/default.html', + ], ctx, context_instance=kwargs.get('context')) diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 59766f865..a1be16ae0 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -7,8 +7,9 @@ from django.utils.translation import ugettext_lazy as _ from feincms.apps import ApplicationContent -from feincms.contents import RawContent, MediaFileContent, TemplateContent +from feincms.contents import RawContent, TemplateContent from feincms.models import Base, create_base_model +from feincms.module.medialibrary.contents import MediaFileContent from feincms.module.page.models import Page from feincms.module.page import processors diff --git a/tests/testapp/tests/test_cms.py b/tests/testapp/tests/test_cms.py index 305be7738..5e76d0b51 100644 --- a/tests/testapp/tests/test_cms.py +++ b/tests/testapp/tests/test_cms.py @@ -9,7 +9,8 @@ from django.db import models from django.test import TestCase -from feincms.contents import RawContent, RichTextContent, MediaFileContent +from feincms.contents import RawContent, RichTextContent +from feincms.module.medialibrary.contents import MediaFileContent from testapp.models import ExampleCMSBase, ExampleCMSBase2 from .test_stuff import Empty From 93e10e36b2906cba41eef782b7ed8ba3ef5ab01f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:12:00 +0200 Subject: [PATCH 284/654] FeinCMS 2.0a9 --- feincms/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index a075b86ed..c6a254ed9 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,8 +1,8 @@ from __future__ import absolute_import, unicode_literals -VERSION = (2, 0, 'a', 8) +VERSION = (2, 0, 'a', 9) # __version__ = '.'.join(map(str, VERSION)) -__version__ = '2.0a8' +__version__ = '2.0a9' class LazySettings(object): From 0e73afe928283c6afa65df6eaa4cc2cb386d0a57 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:28:37 +0200 Subject: [PATCH 285/654] Prepare queryset_transform for Django 1.9 --- feincms/utils/queryset_transform.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py index 51807f951..3755ab5dd 100644 --- a/feincms/utils/queryset_transform.py +++ b/feincms/utils/queryset_transform.py @@ -90,9 +90,10 @@ class TransformQuerySet(models.query.QuerySet): def __init__(self, *args, **kwargs): super(TransformQuerySet, self).__init__(*args, **kwargs) self._transform_fns = [] + self._orig_iterable_class = getattr(self, '_iterable_class', None) - def _clone(self, klass=None, setup=False, **kw): - c = super(TransformQuerySet, self)._clone(klass, setup, **kw) + def _clone(self, *args, **kwargs): + c = super(TransformQuerySet, self)._clone(*args, **kwargs) c._transform_fns = self._transform_fns[:] return c @@ -103,12 +104,18 @@ def transform(self, *fn): def iterator(self): result_iter = super(TransformQuerySet, self).iterator() - if self._transform_fns: - results = list(result_iter) - for fn in self._transform_fns: - fn(results) - return iter(results) - return result_iter + + if not self._transform_fns: + return result_iter + + if getattr(self, '_iterable_class', None) != self._orig_iterable_class: + # Do not process the result of values() and values_list() + return result_iter + + results = list(result_iter) + for fn in self._transform_fns: + fn(results) + return iter(results) if hasattr(models.Manager, 'from_queryset'): From 00e0b1eb3bbb2c4649d3bdecf0eb6bc0dd4c21a1 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:38:00 +0200 Subject: [PATCH 286/654] The return value of feincms_render_region is safe --- feincms/templatetags/feincms_tags.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index 42da0e9e3..a1820e1d1 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -8,6 +8,7 @@ from django import template from django.conf import settings +from django.utils.safestring import mark_safe from feincms.utils import get_singleton, get_singleton_url @@ -46,9 +47,9 @@ def feincms_render_region(context, feincms_object, region, request=None): if not feincms_object: return '' - return ''.join( + return mark_safe(''.join( _render_content(content, request=request, context=context) - for content in getattr(feincms_object.content, region)) + for content in getattr(feincms_object.content, region))) @register.simple_tag(takes_context=True) From d465bd46a330bf0192b9588f4ffcbb5db070f83b Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:38:50 +0200 Subject: [PATCH 287/654] FeinCMS 2.0a10 --- feincms/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index c6a254ed9..7e7233b8c 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,8 +1,8 @@ from __future__ import absolute_import, unicode_literals -VERSION = (2, 0, 'a', 9) +VERSION = (2, 0, 'a', 10) # __version__ = '.'.join(map(str, VERSION)) -__version__ = '2.0a9' +__version__ = '2.0a10' class LazySettings(object): From 7e09d9c821d9dbd72cf60c19708f91a3248d3b95 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:48:50 +0200 Subject: [PATCH 288/654] This certainly works --- feincms/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/models.py b/feincms/models.py index aad088e3e..3c07d91a7 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -416,7 +416,7 @@ def _template(self): (template_.key, template_.title,) for template_ in cls._feincms_templates.values() ] - field.default = field.choices[0][0] + field.default = cls.TEMPLATE_CHOICES[0][0] # Build a set of all regions used anywhere cls._feincms_all_regions = set() From e8cca4c7fb84f709f110967cfa5698b38f93224e Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:03:41 +0200 Subject: [PATCH 289/654] Do not hardcode the change URL --- tests/testapp/tests/test_page.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index d9645b4f3..c4265c893 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -11,8 +11,9 @@ from django.conf import settings from django.contrib.auth.models import User, AnonymousUser from django.contrib.contenttypes.models import ContentType -from django.db import models from django.contrib.sites.models import Site +from django.core.urlresolvers import reverse +from django.db import models from django.http import Http404, HttpResponseBadRequest from django.template import TemplateDoesNotExist from django.template.defaultfilters import slugify @@ -177,10 +178,14 @@ def test_03_item_editor(self): self.login() self.assertRedirects( self.create_page_through_admin(_continue=1), - '/admin/page/page/1/') + reverse('admin:page_page_change', args=(1,))) self.assertEqual( - self.client.get('/admin/page/page/1/').status_code, 200) - self.is_published('/admin/page/page/42/', should_be=False) + self.client.get( + reverse('admin:page_page_change', args=(1,)) + ).status_code, 200) + self.is_published( + reverse('admin:page_page_change', args=(42,)), + should_be=False) def test_03_add_another(self): self.login() @@ -379,7 +384,9 @@ def create_page_through_admincontent(self, page, **kwargs): } data.update(kwargs) - return self.client.post('/admin/page/page/%s/' % page.pk, data) + return self.client.post( + reverse('admin:page_page_change', args=(page.pk,)), + data) def test_09_pagecontent(self): self.create_default_page_set() @@ -461,7 +468,7 @@ def test_10_mediafile_and_imagecontent(self): '%s-ha' % short_language_code() in mf.available_translations) # this should not raise - self.client.get('/admin/page/page/1/') + self.client.get(reverse('admin:page_page_change', args=(1,))) page.mediafilecontent_set.update(mediafile=3) # this should not raise @@ -1139,7 +1146,9 @@ def test_24_admin_redirects(self): page = Page.objects.get(pk=1) response = self.create_page_through_admincontent(page, _continue=1) - self.assertRedirects(response, '/admin/page/page/1/') + self.assertRedirects( + response, + reverse('admin:page_page_change', args=(1,))) response = self.create_page_through_admincontent(page, _addanother=1) self.assertRedirects(response, '/admin/page/page/add/') @@ -1656,5 +1665,7 @@ def test_41_templatecontent(self): # The empty form contains the template option. self.login() self.assertContains( - self.client.get('/admin/page/page/%s/' % page.id), + self.client.get( + reverse('admin:page_page_change', args=(page.id,)) + ), '') From 6ddcdc5dd30d5d1b5d23ff29a7058c1c15f799f2 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:51:08 +0200 Subject: [PATCH 290/654] The prefix= argument has been removed, use semi-official API instead --- feincms/apps/reverse.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/feincms/apps/reverse.py b/feincms/apps/reverse.py index 387e025f9..26ee8cade 100644 --- a/feincms/apps/reverse.py +++ b/feincms/apps/reverse.py @@ -3,14 +3,16 @@ from functools import wraps from django.core.cache import cache -from django.core.urlresolvers import NoReverseMatch, reverse +from django.core.urlresolvers import ( + NoReverseMatch, reverse, get_script_prefix, set_script_prefix +) from django.utils.functional import lazy APP_REVERSE_CACHE_TIMEOUT = 3 -def app_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, +def app_reverse(viewname, urlconf=None, args=None, kwargs=None, *vargs, **vkwargs): """ Reverse URLs from application contents @@ -59,13 +61,17 @@ def app_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, if url_prefix: # vargs and vkwargs are used to send through additional parameters # which are uninteresting to us (such as current_app) - return reverse( - viewname, - url_prefix[0], - args=args, - kwargs=kwargs, - prefix=url_prefix[1], - *vargs, **vkwargs) + prefix = get_script_prefix() + try: + set_script_prefix(url_prefix[1]) + return reverse( + viewname, + url_prefix[0], + args=args, + kwargs=kwargs, + *vargs, **vkwargs) + finally: + set_script_prefix(prefix) raise NoReverseMatch("Unable to find ApplicationContent for %r" % urlconf) From bd34e3c00eb8edf48fe1dc34d178b95899d63885 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:57:14 +0200 Subject: [PATCH 291/654] Fix more test fails --- tests/testapp/applicationcontent_urls.py | 4 ++-- tests/testapp/tests/test_page.py | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/testapp/applicationcontent_urls.py b/tests/testapp/applicationcontent_urls.py index 197907d9f..5a1ba3cb4 100644 --- a/tests/testapp/applicationcontent_urls.py +++ b/tests/testapp/applicationcontent_urls.py @@ -13,7 +13,7 @@ def module_root(request): - return 'module_root' + return HttpResponse('module_root') def args_test(request, kwarg1, kwarg2): @@ -33,7 +33,7 @@ def fragment(request): def redirect(request): - return HttpResponseRedirect('../') + return HttpResponseRedirect(request.build_absolute_uri('../')) def response(request): diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index c4265c893..1c81549e2 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1217,7 +1217,7 @@ def test_25_applicationcontent(self): self.assertRedirects( self.client.get(page.get_absolute_url() + 'redirect/'), - page.get_absolute_url()) + 'http://testserver' + page.get_absolute_url()) self.assertEqual( app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), @@ -1263,21 +1263,23 @@ def test_25_applicationcontent(self): # Ensure ApplicationContent's admin_fields support works properly self.login() - self.assertContains( - self.client.get('/admin/page/page/%d/' % page1.id), - 'exclusive_subpages') - self.assertContains( - self.client.get('/admin/page/page/%d/' % page1.id), - 'custom_field' + response = self.client.get( + reverse('admin:page_page_change', args=(page1.id,)) ) + self.assertContains(response, 'exclusive_subpages') + self.assertContains(response, 'custom_field') + + # Check if admin_fields get populated correctly app_ct = page1.applicationcontent_set.all()[0] app_ct.parameters =\ '{"custom_field":"val42", "exclusive_subpages": false}' app_ct.save() - r = self.client.get('/admin/page/page/%d/' % page1.id) - self.assertContains(r, 'val42') + response = self.client.get( + reverse('admin:page_page_change', args=(page1.id,)) + ) + self.assertContains(response, 'val42') def test_26_page_form_initial(self): self.create_default_page_set() From eb05fe3f50406c7e202c20705cb7b64645ba4de6 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:59:48 +0200 Subject: [PATCH 292/654] flake8, and run tests with Django 1.9a1 --- .travis.yml | 1 + tests/testapp/tests/test_page.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0b2f61814..2190c98c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ python: env: - DJANGO_REQUIREMENT="Django>=1.7,<1.8" - DJANGO_REQUIREMENT="Django>=1.8,<1.9" + - DJANGO_REQUIREMENT="Django==1.9a1" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install -q $DJANGO_REQUIREMENT django-mptt Pillow feedparser flake8 --use-mirrors diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 1c81549e2..52a9051b6 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1270,7 +1270,6 @@ def test_25_applicationcontent(self): self.assertContains(response, 'exclusive_subpages') self.assertContains(response, 'custom_field') - # Check if admin_fields get populated correctly app_ct = page1.applicationcontent_set.all()[0] app_ct.parameters =\ From 118e026faaf5f0799bcf859111293d0d639ca346 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 11:03:00 +0200 Subject: [PATCH 293/654] django-mptt@master --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2190c98c2..5b36a889a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,8 @@ env: - DJANGO_REQUIREMENT="Django==1.9a1" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - - pip install -q $DJANGO_REQUIREMENT django-mptt Pillow feedparser flake8 --use-mirrors +# FIXME revert to a mptt release once it supports Django 1.9 + - pip install -q $DJANGO_REQUIREMENT https://github.com/django-mptt/django-mptt/archive/master.zip Pillow feedparser flake8 --use-mirrors - python setup.py -q install # command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && flake8 ." From 2659908d044678d6cbf90492e898d1aa9e3dc462 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 11:07:50 +0200 Subject: [PATCH 294/654] Django 1.9 will not support Python 3.2 anymore --- .travis.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b36a889a..71d43db96 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,13 +5,16 @@ python: - "3.2" - "3.4" env: - - DJANGO_REQUIREMENT="Django>=1.7,<1.8" - - DJANGO_REQUIREMENT="Django>=1.8,<1.9" - - DJANGO_REQUIREMENT="Django==1.9a1" + - REQ="Django>=1.7,<1.8 django-mptt" + - REQ="Django>=1.8,<1.9 django-mptt" + - REQ="Django==1.9a1 https://github.com/django-mptt/django-mptt/archive/master.zip" +matrix: + exclude: + - python: "3.2" + - env: REQ="Django==1.9a1 https://github.com/django-mptt/django-mptt/archive/master.zip" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: -# FIXME revert to a mptt release once it supports Django 1.9 - - pip install -q $DJANGO_REQUIREMENT https://github.com/django-mptt/django-mptt/archive/master.zip Pillow feedparser flake8 --use-mirrors + - pip install $REQ Pillow feedparser flake8 - python setup.py -q install # command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && flake8 ." From 49becd5134623c9b1c7f9697fa8c8ece3e20448f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 11:08:26 +0200 Subject: [PATCH 295/654] Oops --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 71d43db96..07e1e01e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ env: matrix: exclude: - python: "3.2" - - env: REQ="Django==1.9a1 https://github.com/django-mptt/django-mptt/archive/master.zip" + env: REQ="Django==1.9a1 https://github.com/django-mptt/django-mptt/archive/master.zip" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install $REQ Pillow feedparser flake8 From 210964f470fac36ff6a3eeb56c2c125d7dd65b60 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 10 Oct 2015 12:07:14 +0200 Subject: [PATCH 296/654] Almost everything in ie_compat.js is unused --- feincms/static/feincms/ie_compat.js | 98 ------------------- feincms/static/feincms/item_editor.js | 10 ++ feincms/static/feincms/tree_editor.js | 11 +++ .../templates/admin/feincms/item_editor.html | 1 - .../templates/admin/feincms/tree_editor.html | 1 - 5 files changed, 21 insertions(+), 100 deletions(-) delete mode 100644 feincms/static/feincms/ie_compat.js diff --git a/feincms/static/feincms/ie_compat.js b/feincms/static/feincms/ie_compat.js deleted file mode 100644 index f10fa4b93..000000000 --- a/feincms/static/feincms/ie_compat.js +++ /dev/null @@ -1,98 +0,0 @@ -/* Re-implement some methods IE sadly does not */ - -if(typeof(Array.prototype.indexOf) == 'undefined') { - // indexOf() function prototype for IE6/7/8 compatibility, taken from - // JavaScript Standard Library - http://www.devpro.it/JSL/ - Array.prototype.indexOf=function(elm,i){ - var j=this.length; - if(!i)i=0; - if(i>=0){while(i>> 0; - if (len === 0) { - return -1; - } - - n = len; - if (arguments.length > 1) { - n = Number(arguments[1]); - if (n != n) { - n = 0; - } - else if (n != 0 && n != (1 / 0) && n != -(1 / 0)) { - n = (n > 0 || -1) * Math.floor(Math.abs(n)); - } - } - - for (k = n >= 0 - ? Math.min(n, len - 1) - : len - Math.abs(n); k >= 0; k--) { - if (k in t && t[k] === searchElement) { - return k; - } - } - return -1; - }; -} diff --git a/feincms/static/feincms/item_editor.js b/feincms/static/feincms/item_editor.js index b034a6ab6..8f1c14720 100644 --- a/feincms/static/feincms/item_editor.js +++ b/feincms/static/feincms/item_editor.js @@ -1,3 +1,13 @@ +// IE<9 lacks Array.prototype.indexOf +if (!Array.prototype.indexOf) { + Array.prototype.indexOf = function(needle) { + for (i=0, l=this.length; i - {% include "admin/feincms/_messages_js.html" %} diff --git a/feincms/templates/admin/feincms/tree_editor.html b/feincms/templates/admin/feincms/tree_editor.html index e0629cb08..5d7785054 100644 --- a/feincms/templates/admin/feincms/tree_editor.html +++ b/feincms/templates/admin/feincms/tree_editor.html @@ -11,7 +11,6 @@ feincms.node_levels = {{ node_levels|default:"{}" }}; - {% endblock %} From 627b0aec6ae61d139975c334b6034376ea05d219 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 13 Oct 2015 10:59:51 +0200 Subject: [PATCH 297/654] Add a code of conduct --- CONTRIBUTING.rst | 19 +++++++++++++++++++ MANIFEST.in | 1 + 2 files changed, 20 insertions(+) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 000000000..ddf1d89ba --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,19 @@ +======================= +Contributing to FeinCMS +======================= + +Submission guidelines +===================== + +If you are creating a pull request, fork the repository and make any changes +in your own feature branch. + +Write and run tests. + + +Code of Conduct +=============== + +This project adheres to the +`Open Code of Conduct `_. +By participating, you are expected to honor this code. diff --git a/MANIFEST.in b/MANIFEST.in index 2b605a533..b8a1e6a53 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include AUTHORS +include CONTRIBUTING.rst include LICENSE include MANIFEST.in include README.rst From bc613a747c451feab0a708c102e63f2911163fc9 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 29 Oct 2015 11:18:51 +0100 Subject: [PATCH 298/654] formsets, not formset --- feincms/admin/item_editor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/admin/item_editor.py b/feincms/admin/item_editor.py index 35315f0b7..96295ec07 100644 --- a/feincms/admin/item_editor.py +++ b/feincms/admin/item_editor.py @@ -203,9 +203,9 @@ def change_view(self, request, object_id, **kwargs): return super(ItemEditor, self).change_view( request, object_id, **kwargs) - def save_related(self, request, form, formset, change): + def save_related(self, request, form, formsets, change): super(ItemEditor, self).save_related( - request, form, formset, change) + request, form, formsets, change) itemeditor_post_save_related.send( sender=form.instance.__class__, instance=form.instance, From 30d1e263e1ac32cdd1550517de003791e533b2de Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 29 Oct 2015 11:19:03 +0100 Subject: [PATCH 299/654] FeinCMS 2.0a11 --- feincms/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 7e7233b8c..6b9425f80 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,8 +1,8 @@ from __future__ import absolute_import, unicode_literals -VERSION = (2, 0, 'a', 10) +VERSION = (2, 0, 'a', 11) # __version__ = '.'.join(map(str, VERSION)) -__version__ = '2.0a10' +__version__ = '2.0a11' class LazySettings(object): From 6ae651717e2f7a0c4835aa7998c29f2b0fa14afa Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Tue, 24 Nov 2015 17:10:50 +0100 Subject: [PATCH 300/654] When doing a lookup for a FEINCMS_CMS_404_PAGE lookup, clear out the cached page attribute on request, so we actually do a new lookup and not re-raise the 404 we just caught. See issue #575 --- feincms/views/cbv/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/feincms/views/cbv/views.py b/feincms/views/cbv/views.py index 5b1e74153..27b588bb2 100644 --- a/feincms/views/cbv/views.py +++ b/feincms/views/cbv/views.py @@ -42,8 +42,12 @@ def dispatch(self, request, *args, **kwargs): # page. # Note: request.path is used by the page redirect processor # to determine if the redirect can be taken, must be == to - # page url - request.path = settings.FEINCMS_CMS_404_PAGE + # page url. + # Also clear out the _feincms_page attribute which caches + # page lookups (and would just re-raise a 404). + request.path = request.path_info = settings.FEINCMS_CMS_404_PAGE + if hasattr(request, '_feincms_page'): + delattr(request, '_feincms_page') response = super(Handler, self).dispatch( request, settings.FEINCMS_CMS_404_PAGE, **kwargs) # Only set status if we actually have a page. If we get for From a40e6bfa0f1993a7be359006f13579bb334fe374 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Mon, 30 Nov 2015 14:13:12 +0100 Subject: [PATCH 301/654] Work around new warning in Django 1.8 which caused a warning for Page, MediaFile, Category, MediaFileTranslation: RemovedInDjango19Warning: Model class ... doesn't declare an explicit app_label and either isn't in an application in INSTALLED_APPS or else was imported before its application was loaded. This will no longer be supported in Django 1.9. --- feincms/module/medialibrary/models.py | 5 ++++- feincms/module/page/models.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/feincms/module/medialibrary/models.py b/feincms/module/medialibrary/models.py index 21d9799bf..2896b56ba 100644 --- a/feincms/module/medialibrary/models.py +++ b/feincms/module/medialibrary/models.py @@ -54,6 +54,7 @@ class Meta: ordering = ['parent__title', 'title'] verbose_name = _('category') verbose_name_plural = _('categories') + app_label = 'medialibrary' objects = CategoryManager() @@ -240,7 +241,8 @@ def delete_mediafile(self, name=None): # ------------------------------------------------------------------------ class MediaFile(MediaFileBase): - pass + class Meta: + app_label = 'medialibrary' @receiver(post_delete, sender=MediaFile) @@ -264,6 +266,7 @@ class Meta: verbose_name = _('media file translation') verbose_name_plural = _('media file translations') unique_together = ('parent', 'language_code') + app_label = 'medialibrary' def __str__(self): return self.caption diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index 9e1bf94d9..ac2fa3707 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -426,6 +426,7 @@ class Meta: ordering = ['tree_id', 'lft'] verbose_name = _('page') verbose_name_plural = _('pages') + app_label = 'page' # not yet # permissions = (("edit_page", _("Can edit page metadata")),) Page.register_default_processors() From 32bbfd3add6d6ffe378fc50627769e4341f38b8e Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Mon, 30 Nov 2015 15:46:24 +0100 Subject: [PATCH 302/654] get_navigation_url could never have worked right: When having an internal link "page.page:13", it would return the url of the current page. Even if the comparison had been right, then the returned url would have been "page.page:13", which is totally bogus for something to put into an tag. Now tries to resolve the redirect into a real url. Also factor out a get_redirect_to_target(), so that one can get the real internal target of a page in templates too. --- feincms/module/page/models.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index ac2fa3707..0d25374c6 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -329,10 +329,9 @@ def get_navigation_url(self): Return either ``redirect_to`` if it is set, or the URL of this page. """ - # :-( maybe this could be cleaned up a bit? - if not self.redirect_to or REDIRECT_TO_RE.match(self.redirect_to): - return self._cached_url - return self.redirect_to + if self.redirect_to: + return self.get_redirect_to_target() + return self._cached_url cache_key_components = [ lambda p: getattr(django_settings, 'SITE_ID', 0), @@ -374,34 +373,45 @@ def last_modified(self, request): """ return None - def get_redirect_to_target(self, request): + def get_redirect_to_page(self): """ This might be overriden/extended by extension modules. """ if not self.redirect_to: - return '' + return None # It might be an identifier for a different object match = REDIRECT_TO_RE.match(self.redirect_to) # It's not, oh well. if not match: - return self.redirect_to + return None matches = match.groupdict() model = get_model(matches['app_label'], matches['model_name']) if not model: - return self.redirect_to + return None try: instance = model._default_manager.get(pk=int(matches['pk'])) - return instance.get_absolute_url() + return instance except models.ObjectDoesNotExist: pass - return self.redirect_to + return None + + def get_redirect_to_target(self, request=None): + """ + This might be overriden/extended by extension modules. + """ + + target_page = self.get_redirect_to_page() + if target_page is None: + return self.redirect_to + + return target_page.get_absolute_url() @classmethod def path_to_cache_key(cls, path): From 470af4162c98087a32713f9dfd345102f615ea20 Mon Sep 17 00:00:00 2001 From: valmynd Date: Thu, 10 Dec 2015 01:50:36 +0100 Subject: [PATCH 303/654] make FeinCMS compatible with django-reversion >= 1.10 see http://django-reversion.readthedocs.org/en/latest/api.html see https://github.com/etianen/django-reversion/commit/755de5cc471b83b92aa731c0e58b452d205ed414 --- feincms/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/models.py b/feincms/models.py index e40ac9411..6406b8619 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -820,7 +820,7 @@ def replace_content_with(self, obj): @classmethod def register_with_reversion(cls): try: - import reversion + from reversion import revisions as reversion except ImportError: raise EnvironmentError("django-reversion is not installed") From b3042a8f65718b6c81e547516c728fc281cda7a1 Mon Sep 17 00:00:00 2001 From: valmynd Date: Fri, 11 Dec 2015 10:35:16 +0100 Subject: [PATCH 304/654] make FeinCMS compatible with django-reversion >= 1.10, but also backwards compatible --- feincms/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feincms/models.py b/feincms/models.py index 6406b8619..53ec7be19 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -822,7 +822,10 @@ def register_with_reversion(cls): try: from reversion import revisions as reversion except ImportError: - raise EnvironmentError("django-reversion is not installed") + try: + import reversion + except ImportError: + raise EnvironmentError("django-reversion is not installed") follow = [] for content_type in cls._feincms_content_types: From f2fa08609048e145de4fc6e54a0fe7315481038a Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 19 Aug 2015 09:35:55 +0200 Subject: [PATCH 305/654] Remove an empty file --- feincms/admin/thumbnail.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 feincms/admin/thumbnail.py diff --git a/feincms/admin/thumbnail.py b/feincms/admin/thumbnail.py deleted file mode 100644 index e69de29bb..000000000 From 1ce3145dac6f903df32f196e45c3cc1aba7ba8f2 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 19 Aug 2015 09:36:05 +0200 Subject: [PATCH 306/654] Add comment to empty template --- tests/testapp/templates/feincms_base.html | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testapp/templates/feincms_base.html b/tests/testapp/templates/feincms_base.html index e69de29bb..3cac88c3a 100644 --- a/tests/testapp/templates/feincms_base.html +++ b/tests/testapp/templates/feincms_base.html @@ -0,0 +1 @@ +{# only has to exist #} From b4b8963064cde77f044311fc22a114108aca6adc Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 22 Aug 2015 10:07:26 +0200 Subject: [PATCH 307/654] Default setting update to TinyMCE 4.2 --- feincms/default_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/default_settings.py b/feincms/default_settings.py index 455c4ec22..dafe42a7e 100644 --- a/feincms/default_settings.py +++ b/feincms/default_settings.py @@ -48,7 +48,7 @@ FEINCMS_RICHTEXT_INIT_CONTEXT = getattr( settings, 'FEINCMS_RICHTEXT_INIT_CONTEXT', { - 'TINYMCE_JS_URL': '//tinymce.cachefly.net/4.1/tinymce.min.js', + 'TINYMCE_JS_URL': '//tinymce.cachefly.net/4.2/tinymce.min.js', 'TINYMCE_DOMAIN': None, 'TINYMCE_CONTENT_CSS_URL': None, 'TINYMCE_LINK_LIST_URL': None From 7cc9778527472ea262165cedb023fe5edf56189c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 29 Aug 2015 13:52:51 +0200 Subject: [PATCH 308/654] Allow running cov.sh without activated venv --- tests/cov.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/cov.sh b/tests/cov.sh index e02eb0dd3..c577a1f65 100755 --- a/tests/cov.sh +++ b/tests/cov.sh @@ -1,3 +1,3 @@ #!/bin/sh -coverage run --branch --include="*feincms/feincms*" ./manage.py test testapp -coverage html +venv/bin/coverage run --branch --include="*feincms/feincms*" ./manage.py test testapp +venv/bin/coverage html From 5d856d04f37a8a214167f6b8fa7bf8ab9bf46e81 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 29 Aug 2015 16:28:44 +0200 Subject: [PATCH 309/654] Remove an unused, internal method --- feincms/extensions.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/feincms/extensions.py b/feincms/extensions.py index 6dcc37397..31773ff6f 100644 --- a/feincms/extensions.py +++ b/feincms/extensions.py @@ -92,13 +92,6 @@ def handle_modeladmin(self, modeladmin): pass -def _ensure_list(cls, attribute): - if cls is None: - return - value = getattr(cls, attribute, ()) or () - setattr(cls, attribute, list(value)) - - class ExtensionModelAdmin(admin.ModelAdmin): def __init__(self, *args, **kwargs): super(ExtensionModelAdmin, self).__init__(*args, **kwargs) From b2042235ffd8ceb6ae0d2c8d6ad3f501da15deac Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 29 Aug 2015 16:51:17 +0200 Subject: [PATCH 310/654] Move import to top of file (especially since we import this file anyway) --- feincms/module/page/extensions/navigation.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feincms/module/page/extensions/navigation.py b/feincms/module/page/extensions/navigation.py index 1254fbd6a..f49e86b00 100644 --- a/feincms/module/page/extensions/navigation.py +++ b/feincms/module/page/extensions/navigation.py @@ -22,7 +22,7 @@ from django.utils.translation import ugettext_lazy as _ from feincms import extensions -from feincms.utils import get_object +from feincms.utils import get_object, shorten_string from feincms._internal import monkeypatch_method @@ -80,7 +80,6 @@ def get_original_translation(self, page): return page def short_title(self): - from feincms.utils import shorten_string return shorten_string(self.title) From d2ebb36828245c498d2274b6055bfa8b9d21e133 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 31 Aug 2015 23:04:09 +0200 Subject: [PATCH 311/654] We do not need that, 3.2 and 3.4 are good enough --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 67e497146..9ea146933 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ python: - "2.6" - "2.7" - "3.2" - - "3.3" - "3.4" env: - DJANGO_REQUIREMENT="Django>=1.6,<1.7" From b32afffe90b8fca180059a954b09b8bea8bdbf5c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 3 Sep 2015 10:34:52 +0200 Subject: [PATCH 312/654] Stop crashing if `feincms_page` is None or "" --- feincms/templatetags/feincms_page_tags.py | 2 +- feincms/templatetags/feincms_tags.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py index 6a56ccfd7..132a4b8f1 100644 --- a/feincms/templatetags/feincms_page_tags.py +++ b/feincms/templatetags/feincms_page_tags.py @@ -47,7 +47,7 @@ def feincms_nav(context, feincms_page, level=1, depth=1, group=None): page_class = _get_page_model() - if feincms_page is None: + if not feincms_page: return [] if isinstance(feincms_page, HttpRequest): diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index 20d25e0cf..42da0e9e3 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -43,6 +43,9 @@ def feincms_render_region(context, feincms_object, region, request=None): """ {% feincms_render_region feincms_page "main" request %} """ + if not feincms_object: + return '' + return ''.join( _render_content(content, request=request, context=context) for content in getattr(feincms_object.content, region)) @@ -53,6 +56,9 @@ def feincms_render_content(context, content, request=None): """ {% feincms_render_content content request %} """ + if not content: + return '' + return _render_content(content, request=request, context=context) From 11c20b29eeba504bbdbdc8853a5e439699f8532c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 4 Sep 2015 21:23:48 +0200 Subject: [PATCH 313/654] .clear() also empties a dict --- feincms/module/extensions/ct_tracker.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feincms/module/extensions/ct_tracker.py b/feincms/module/extensions/ct_tracker.py index 2065a45c4..44cd01fd4 100644 --- a/feincms/module/extensions/ct_tracker.py +++ b/feincms/module/extensions/ct_tracker.py @@ -126,8 +126,7 @@ def class_prepared_handler(sender, **kwargs): # are fully loaded and initialized when the translation map is accessed. # This leads to (lots of) crashes on the server. Better be safe and # kill the translation map when any class_prepared signal is received. - global _translation_map_cache - _translation_map_cache = {} + _translation_map_cache.clear() class_prepared.connect(class_prepared_handler) From 42b1f009ec351194049246b08296b538feb299e9 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 8 Sep 2015 15:21:30 +0200 Subject: [PATCH 314/654] Fix #597: Boolean fields require default values --- feincms/module/extensions/featured.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/feincms/module/extensions/featured.py b/feincms/module/extensions/featured.py index 6930a58a7..efc348096 100644 --- a/feincms/module/extensions/featured.py +++ b/feincms/module/extensions/featured.py @@ -12,7 +12,13 @@ class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('featured', models.BooleanField(_('featured'))) + self.model.add_to_class( + 'featured', + models.BooleanField( + _('featured'), + default=False, + ), + ) if hasattr(self.model, 'cache_key_components'): self.model.cache_key_components.append(lambda page: page.featured) From e2cc400073c492192a014b036ba2356545ae1fdb Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:28:37 +0200 Subject: [PATCH 315/654] Prepare queryset_transform for Django 1.9 --- feincms/utils/queryset_transform.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py index e2961965b..ac07ac2d9 100644 --- a/feincms/utils/queryset_transform.py +++ b/feincms/utils/queryset_transform.py @@ -90,9 +90,10 @@ class TransformQuerySet(models.query.QuerySet): def __init__(self, *args, **kwargs): super(TransformQuerySet, self).__init__(*args, **kwargs) self._transform_fns = [] + self._orig_iterable_class = getattr(self, '_iterable_class', None) - def _clone(self, klass=None, setup=False, **kw): - c = super(TransformQuerySet, self)._clone(klass, setup, **kw) + def _clone(self, *args, **kwargs): + c = super(TransformQuerySet, self)._clone(*args, **kwargs) c._transform_fns = self._transform_fns[:] return c @@ -103,12 +104,18 @@ def transform(self, *fn): def iterator(self): result_iter = super(TransformQuerySet, self).iterator() - if self._transform_fns: - results = list(result_iter) - for fn in self._transform_fns: - fn(results) - return iter(results) - return result_iter + + if not self._transform_fns: + return result_iter + + if getattr(self, '_iterable_class', None) != self._orig_iterable_class: + # Do not process the result of values() and values_list() + return result_iter + + results = list(result_iter) + for fn in self._transform_fns: + fn(results) + return iter(results) if hasattr(models.Manager, 'from_queryset'): From a11633a3e5a27c2a22f310980548d9726208f7af Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:38:00 +0200 Subject: [PATCH 316/654] The return value of feincms_render_region is safe --- feincms/templatetags/feincms_tags.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index 42da0e9e3..a1820e1d1 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -8,6 +8,7 @@ from django import template from django.conf import settings +from django.utils.safestring import mark_safe from feincms.utils import get_singleton, get_singleton_url @@ -46,9 +47,9 @@ def feincms_render_region(context, feincms_object, region, request=None): if not feincms_object: return '' - return ''.join( + return mark_safe(''.join( _render_content(content, request=request, context=context) - for content in getattr(feincms_object.content, region)) + for content in getattr(feincms_object.content, region))) @register.simple_tag(takes_context=True) From d851a981409f957e722be1962bfd25986ad9d602 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 09:48:50 +0200 Subject: [PATCH 317/654] This certainly works --- feincms/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/models.py b/feincms/models.py index e40ac9411..098eca43b 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -422,7 +422,7 @@ def _template(self): (template_.key, template_.title,) for template_ in cls._feincms_templates.values() ] - field.default = field.choices[0][0] + field.default = cls.TEMPLATE_CHOICES[0][0] # Build a set of all regions used anywhere cls._feincms_all_regions = set() From 7a046856a48659eb6fae630a80aae0ad019a37b6 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 13 Oct 2015 10:59:51 +0200 Subject: [PATCH 318/654] Add a code of conduct --- CONTRIBUTING.rst | 19 +++++++++++++++++++ MANIFEST.in | 1 + 2 files changed, 20 insertions(+) create mode 100644 CONTRIBUTING.rst diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 000000000..ddf1d89ba --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,19 @@ +======================= +Contributing to FeinCMS +======================= + +Submission guidelines +===================== + +If you are creating a pull request, fork the repository and make any changes +in your own feature branch. + +Write and run tests. + + +Code of Conduct +=============== + +This project adheres to the +`Open Code of Conduct `_. +By participating, you are expected to honor this code. diff --git a/MANIFEST.in b/MANIFEST.in index 2b605a533..b8a1e6a53 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include AUTHORS +include CONTRIBUTING.rst include LICENSE include MANIFEST.in include README.rst From 27791206d17a11cef185d62d072e0a62f799a311 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:51:08 +0200 Subject: [PATCH 319/654] The prefix= argument has been removed, use semi-official API instead --- feincms/content/application/models.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index ccee39238..84c224a84 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -12,7 +12,9 @@ from django.conf import settings from django.core.cache import cache from django.core.urlresolvers import ( - Resolver404, resolve, reverse, NoReverseMatch) + Resolver404, resolve, reverse, NoReverseMatch, + get_script_prefix, set_script_prefix, +) from django.db import models from django.db.models import signals from django.http import HttpResponse @@ -54,7 +56,7 @@ def cycle_app_reverse_cache(*args, **kwargs): cycle_app_reverse_cache() -def app_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, +def app_reverse(viewname, urlconf=None, args=None, kwargs=None, *vargs, **vkwargs): """ Reverse URLs from application contents @@ -102,13 +104,17 @@ def app_reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, if url_prefix: # vargs and vkwargs are used to send through additional parameters # which are uninteresting to us (such as current_app) - return reverse( - viewname, - url_prefix[0], - args=args, - kwargs=kwargs, - prefix=url_prefix[1], - *vargs, **vkwargs) + prefix = get_script_prefix() + try: + set_script_prefix(url_prefix[1]) + return reverse( + viewname, + url_prefix[0], + args=args, + kwargs=kwargs, + *vargs, **vkwargs) + finally: + set_script_prefix(prefix) raise NoReverseMatch("Unable to find ApplicationContent for %r" % urlconf) From 44cf2b92a2ab78c513b5a5623fbd8c4884f64733 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:03:41 +0200 Subject: [PATCH 320/654] Do not hardcode the change URL --- tests/testapp/tests/test_page.py | 40 +++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 3308db3a2..c0255606d 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -15,6 +15,8 @@ from django.core import mail from django.db import models from django.contrib.sites.models import Site +from django.core.urlresolvers import reverse +from django.db import models from django.http import Http404, HttpResponseBadRequest from django.template import TemplateDoesNotExist from django.template.defaultfilters import slugify @@ -178,10 +180,14 @@ def test_03_item_editor(self): self.login() self.assertRedirects( self.create_page_through_admin(_continue=1), - '/admin/page/page/1/') + reverse('admin:page_page_change', args=(1,))) self.assertEqual( - self.client.get('/admin/page/page/1/').status_code, 200) - self.is_published('/admin/page/page/42/', should_be=False) + self.client.get( + reverse('admin:page_page_change', args=(1,)) + ).status_code, 200) + self.is_published( + reverse('admin:page_page_change', args=(42,)), + should_be=False) def test_03_add_another(self): self.login() @@ -391,7 +397,9 @@ def create_page_through_admincontent(self, page, **kwargs): } data.update(kwargs) - return self.client.post('/admin/page/page/%s/' % page.pk, data) + return self.client.post( + reverse('admin:page_page_change', args=(page.pk,)), + data) def test_09_pagecontent(self): self.create_default_page_set() @@ -475,7 +483,7 @@ def test_10_mediafile_and_imagecontent(self): '%s-ha' % short_language_code() in mf.available_translations) # this should not raise - self.client.get('/admin/page/page/1/') + self.client.get(reverse('admin:page_page_change', args=(1,))) # self.assertTrue('alt="something"' in page.content.main[1].render()) # Since it isn't an image @@ -1210,7 +1218,9 @@ def test_24_admin_redirects(self): page = Page.objects.get(pk=1) response = self.create_page_through_admincontent(page, _continue=1) - self.assertRedirects(response, '/admin/page/page/1/') + self.assertRedirects( + response, + reverse('admin:page_page_change', args=(1,))) response = self.create_page_through_admincontent(page, _addanother=1) self.assertRedirects(response, '/admin/page/page/add/') @@ -1734,3 +1744,21 @@ def test_40_page_is_active(self): {'feincms_page': page1}, p, path='/test-page/whatsup/test/')) self.assertFalse(feincms_page_tags.page_is_active( {'feincms_page': page2}, p, path='/test-page/')) + + def test_41_templatecontent(self): + page = self.create_page() + template = page.templatecontent_set.create( + region=0, + ordering=1, + template='templatecontent_1.html', + ) + + self.assertEqual(template.render(), 'TemplateContent_1\n') + + # The empty form contains the template option. + self.login() + self.assertContains( + self.client.get( + reverse('admin:page_page_change', args=(page.id,)) + ), + '') From f5477728a9d38862b2c3f0148fb27614acfcbaf0 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 30 Sep 2015 10:57:14 +0200 Subject: [PATCH 321/654] Fix more test failures --- feincms/module/medialibrary/modeladmins.py | 2 +- tests/testapp/applicationcontent_urls.py | 4 +-- tests/testapp/tests/test_page.py | 38 +++++++--------------- tests/testapp/tests/test_stuff.py | 13 ++++++-- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 3bbd9da3a..6c701a3f2 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -183,7 +183,7 @@ def file_type(self, obj): d = get_image_dimensions(obj.file.file) if d: t += " %d×%d" % (d[0], d[1]) - except (IOError, ValueError) as e: + except (IOError, TypeError, ValueError) as e: t += " (%s)" % e return t file_type.admin_order_field = 'type' diff --git a/tests/testapp/applicationcontent_urls.py b/tests/testapp/applicationcontent_urls.py index a71092254..7b83b46bd 100644 --- a/tests/testapp/applicationcontent_urls.py +++ b/tests/testapp/applicationcontent_urls.py @@ -13,7 +13,7 @@ def module_root(request): - return 'module_root' + return HttpResponse('module_root') def args_test(request, kwarg1, kwarg2): @@ -33,7 +33,7 @@ def fragment(request): def redirect(request): - return HttpResponseRedirect('../') + return HttpResponseRedirect(request.build_absolute_uri('../')) def response(request): diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index c0255606d..a23a238f8 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1300,7 +1300,7 @@ def test_25_applicationcontent(self): self.assertRedirects( self.client.get(page.get_absolute_url() + 'redirect/'), - page.get_absolute_url()) + 'http://testserver' + page.get_absolute_url()) self.assertEqual( app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), @@ -1357,21 +1357,23 @@ def test_25_applicationcontent(self): # Ensure ApplicationContent's admin_fields support works properly self.login() - self.assertContains( - self.client.get('/admin/page/page/%d/' % page.id), - 'exclusive_subpages') - self.assertContains( - self.client.get('/admin/page/page/%d/' % page.id), - 'custom_field' + response = self.client.get( + reverse('admin:page_page_change', args=(page1.id,)) ) + self.assertContains(response, 'exclusive_subpages') + self.assertContains(response, 'custom_field') + + # Check if admin_fields get populated correctly app_ct = page.applicationcontent_set.all()[0] app_ct.parameters =\ '{"custom_field":"val42", "exclusive_subpages": false}' app_ct.save() - r = self.client.get('/admin/page/page/%d/' % page.id) - self.assertContains(r, 'val42') + response = self.client.get( + reverse('admin:page_page_change', args=(page1.id,)) + ) + self.assertContains(response, 'val42') def test_26_page_form_initial(self): self.create_default_page_set() @@ -1744,21 +1746,3 @@ def test_40_page_is_active(self): {'feincms_page': page1}, p, path='/test-page/whatsup/test/')) self.assertFalse(feincms_page_tags.page_is_active( {'feincms_page': page2}, p, path='/test-page/')) - - def test_41_templatecontent(self): - page = self.create_page() - template = page.templatecontent_set.create( - region=0, - ordering=1, - template='templatecontent_1.html', - ) - - self.assertEqual(template.render(), 'TemplateContent_1\n') - - # The empty form contains the template option. - self.login() - self.assertContains( - self.client.get( - reverse('admin:page_page_change', args=(page.id,)) - ), - '') diff --git a/tests/testapp/tests/test_stuff.py b/tests/testapp/tests/test_stuff.py index 058e2235e..cf5c747e6 100644 --- a/tests/testapp/tests/test_stuff.py +++ b/tests/testapp/tests/test_stuff.py @@ -7,6 +7,7 @@ import doctest from django.contrib.auth.models import User +from django.core.urlresolvers import reverse from django.test import TestCase from django.utils.encoding import force_text @@ -131,7 +132,11 @@ def test_01_smoke_test_admin(self): self.assertEqual( self.client.get('/admin/blog/entry/').status_code, 200) self.assertEqual( - self.client.get('/admin/blog/entry/1/').status_code, 200) + self.client.get( + reverse('admin:blog_entry_change', args=(1,)), + ).status_code, + 200, + ) def test_02_translations(self): self.create_entries() @@ -148,4 +153,8 @@ def test_03_admin(self): self.assertEqual( self.client.get('/admin/blog/entry/').status_code, 200) self.assertEqual( - self.client.get('/admin/blog/entry/1/').status_code, 200) + self.client.get( + reverse('admin:blog_entry_change', args=(1,)) + ).status_code, + 200, + ) From 5e177da8fdd5abf29c5bfdaa5cfb5a255cd560de Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:28:24 +0100 Subject: [PATCH 322/654] Oops --- tests/testapp/tests/test_page.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index a23a238f8..b1bc1c300 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1358,7 +1358,7 @@ def test_25_applicationcontent(self): # Ensure ApplicationContent's admin_fields support works properly self.login() response = self.client.get( - reverse('admin:page_page_change', args=(page1.id,)) + reverse('admin:page_page_change', args=(page.id,)) ) self.assertContains(response, 'exclusive_subpages') @@ -1371,7 +1371,7 @@ def test_25_applicationcontent(self): '{"custom_field":"val42", "exclusive_subpages": false}' app_ct.save() response = self.client.get( - reverse('admin:page_page_change', args=(page1.id,)) + reverse('admin:page_page_change', args=(page.id,)) ) self.assertContains(response, 'val42') From 95e5857c5f19aaddea174fc4368d0814a3aa908d Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:29:22 +0100 Subject: [PATCH 323/654] Travis: Django 1.9 --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9ea146933..bfb7c34be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,12 +9,17 @@ env: - DJANGO_REQUIREMENT="Django>=1.6,<1.7" - DJANGO_REQUIREMENT="Django>=1.7,<1.8" - DJANGO_REQUIREMENT="Django>=1.8,<1.9" + - DJANGO_REQUIREMENT="Django>=1.9,<1.10" matrix: exclude: - python: "2.6" env: DJANGO_REQUIREMENT="Django>=1.7,<1.8" - python: "2.6" env: DJANGO_REQUIREMENT="Django>=1.8,<1.9" + - python: "2.6" + env: DJANGO_REQUIREMENT="Django>=1.9,<1.10" + - python: "3.2" + env: DJANGO_REQUIREMENT="Django>=1.9,<1.10" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install -q $DJANGO_REQUIREMENT django-mptt Pillow feedparser flake8 --use-mirrors From da33a6333925aee9fa1c1ca6b177d0f3617e2ff6 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:31:51 +0100 Subject: [PATCH 324/654] flake8 --- feincms/admin/filters.py | 2 +- feincms/module/extensions/translations.py | 12 +++++++----- feincms/views/cbv/views.py | 3 ++- tests/testapp/tests/test_page.py | 2 -- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/feincms/admin/filters.py b/feincms/admin/filters.py index 2f8f9c380..9a126299c 100644 --- a/feincms/admin/filters.py +++ b/feincms/admin/filters.py @@ -73,7 +73,7 @@ def __init__(self, f, request, params, model, model_admin, f, request, params, model, model_admin, field_path) # Restrict results to categories which are actually in use: - if DJANGO_VERSION < (1,8): + if DJANGO_VERSION < (1, 8): related_model = f.related.parent_model related_name = f.related.var_name else: diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index 2a469a673..1ec9ee995 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -34,6 +34,8 @@ # ------------------------------------------------------------------------ logger = logging.getLogger(__name__) +LANGUAGE_COOKIE_NAME = django_settings.LANGUAGE_COOKIE_NAME + # ------------------------------------------------------------------------ def user_has_language_set(request): @@ -43,9 +45,9 @@ def user_has_language_set(request): site's language settings, after all, the user's decision is what counts. """ if (hasattr(request, 'session') - and request.session.get(django_settings.LANGUAGE_COOKIE_NAME) is not None): + and request.session.get(LANGUAGE_COOKIE_NAME) is not None): return True - if django_settings.LANGUAGE_COOKIE_NAME in request.COOKIES: + if LANGUAGE_COOKIE_NAME in request.COOKIES: return True return False @@ -85,8 +87,8 @@ def translation_set_language(request, select_language): if hasattr(request, 'session'): # User has a session, then set this language there - if select_language != request.session.get(django_settings.LANGUAGE_COOKIE_NAME): - request.session[django_settings.LANGUAGE_COOKIE_NAME] = select_language + if select_language != request.session.get(LANGUAGE_COOKIE_NAME): + request.session[LANGUAGE_COOKIE_NAME] = select_language elif request.method == 'GET' and not fallback: # No session is active. We need to set a cookie for the language # so that it persists when users change their location to somewhere @@ -95,7 +97,7 @@ def translation_set_language(request, select_language): # POST requests) response = HttpResponseRedirect(request.get_full_path()) response.set_cookie( - str(django_settings.LANGUAGE_COOKIE_NAME), select_language) + str(LANGUAGE_COOKIE_NAME), select_language) return response diff --git a/feincms/views/cbv/views.py b/feincms/views/cbv/views.py index 27b588bb2..496dcfbb6 100644 --- a/feincms/views/cbv/views.py +++ b/feincms/views/cbv/views.py @@ -45,7 +45,8 @@ def dispatch(self, request, *args, **kwargs): # page url. # Also clear out the _feincms_page attribute which caches # page lookups (and would just re-raise a 404). - request.path = request.path_info = settings.FEINCMS_CMS_404_PAGE + request.path = request.path_info =\ + settings.FEINCMS_CMS_404_PAGE if hasattr(request, '_feincms_page'): delattr(request, '_feincms_page') response = super(Handler, self).dispatch( diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index b1bc1c300..ea930cf06 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -13,7 +13,6 @@ from django.contrib.auth.models import User, AnonymousUser from django.contrib.contenttypes.models import ContentType from django.core import mail -from django.db import models from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.db import models @@ -1364,7 +1363,6 @@ def test_25_applicationcontent(self): self.assertContains(response, 'exclusive_subpages') self.assertContains(response, 'custom_field') - # Check if admin_fields get populated correctly app_ct = page.applicationcontent_set.all()[0] app_ct.parameters =\ From b069e2d50faa46b5357208c2338569cf3c9bf81d Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:36:59 +0100 Subject: [PATCH 325/654] No stable release of django-mptt supports Django 1.9, yet --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bfb7c34be..5d9bcf2b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ matrix: env: DJANGO_REQUIREMENT="Django>=1.9,<1.10" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - - pip install -q $DJANGO_REQUIREMENT django-mptt Pillow feedparser flake8 --use-mirrors + - pip install -q $DJANGO_REQUIREMENT https://github.com/django-mptt/django-mptt/archive/master.zip Pillow feedparser flake8 --use-mirrors - python setup.py -q install # command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && flake8 ." From 850ca08a9cf224b2a0ee0b69ba165edecc6e0731 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:39:38 +0100 Subject: [PATCH 326/654] Travis django-mptt again --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5d9bcf2b7..3737baa93 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,23 +6,23 @@ python: - "3.2" - "3.4" env: - - DJANGO_REQUIREMENT="Django>=1.6,<1.7" - - DJANGO_REQUIREMENT="Django>=1.7,<1.8" - - DJANGO_REQUIREMENT="Django>=1.8,<1.9" - - DJANGO_REQUIREMENT="Django>=1.9,<1.10" + - REQ="Django>=1.6,<1.7 django-mptt" + - REQ="Django>=1.7,<1.8 django-mptt" + - REQ="Django>=1.8,<1.9 django-mptt" + - REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" matrix: exclude: - python: "2.6" - env: DJANGO_REQUIREMENT="Django>=1.7,<1.8" + env: REQ="Django>=1.7,<1.8 django-mptt" - python: "2.6" - env: DJANGO_REQUIREMENT="Django>=1.8,<1.9" + env: REQ="Django>=1.8,<1.9 django-mptt" - python: "2.6" - env: DJANGO_REQUIREMENT="Django>=1.9,<1.10" + env: REQ="Django>=1.9,<1.10 django-mptt" - python: "3.2" - env: DJANGO_REQUIREMENT="Django>=1.9,<1.10" + env: REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - - pip install -q $DJANGO_REQUIREMENT https://github.com/django-mptt/django-mptt/archive/master.zip Pillow feedparser flake8 --use-mirrors + - pip install -q $REQ Pillow feedparser flake8 --use-mirrors - python setup.py -q install # command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && flake8 ." From 81a59c8934f428f50877b24af28578154318f07b Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:41:53 +0100 Subject: [PATCH 327/654] And again --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3737baa93..c3311d5b4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ matrix: - python: "2.6" env: REQ="Django>=1.8,<1.9 django-mptt" - python: "2.6" - env: REQ="Django>=1.9,<1.10 django-mptt" + env: REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" - python: "3.2" env: REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors From 9251c75b004a82b713a4366dd696d2cd1a1a808a Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 10:48:44 +0100 Subject: [PATCH 328/654] FeinCMS v1.11.2 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 727a5281e..657f992a1 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 11, 1) +VERSION = (1, 11, 2) __version__ = '.'.join(map(str, VERSION)) From 8e43e13fc218b61f6a83a50eb682fc12e8274ed3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 12:08:27 +0100 Subject: [PATCH 329/654] Stop failing with admin.E023 --- feincms/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/feincms/models.py b/feincms/models.py index a217b3fd1..ecbb9a952 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -399,7 +399,11 @@ def register_templates(cls, *templates): except (StopIteration,): cls.add_to_class( 'template_key', - models.CharField(_('template'), max_length=255, choices=()) + models.CharField(_('template'), max_length=255, choices=( + # Dummy choice to trick Django. Cannot be empty, + # otherwise admin.E023 happens. + ('base', 'base'), + )) ) field = next(iter( field for field in cls._meta.local_fields From b05e3878b0276002bda644a3c6f796a8fa357725 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 12:09:18 +0100 Subject: [PATCH 330/654] FeinCMS v1.11.3 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 657f992a1..3214ca367 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 11, 2) +VERSION = (1, 11, 3) __version__ = '.'.join(map(str, VERSION)) From e61262b974693071a61fb910129519645d74b543 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Dec 2015 15:34:33 +0100 Subject: [PATCH 331/654] Fix #598: models, not v2 --- docs/contenttypes.rst | 4 ++-- docs/medialibrary.rst | 2 +- feincms/content/medialibrary/models.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/contenttypes.rst b/docs/contenttypes.rst index 6f1078760..2e5d61440 100644 --- a/docs/contenttypes.rst +++ b/docs/contenttypes.rst @@ -354,7 +354,7 @@ Additional arguments for :func:`~feincms.models.Base.create_content_type`: Media library integration ------------------------- -.. module:: feincms.content.medialibrary.v2 +.. module:: feincms.content.medialibrary.models .. class:: MediaFileContent() Mini-framework for arbitrary file types with customizable rendering @@ -552,7 +552,7 @@ do is adding a classmethod named :func:`initialize_type` to your content type, a pass additional keyword arguments to :func:`create_content_type`. If you want to see an example of these two uses, have a look at the -:class:`~feincms.content.medialibrary.v2.MediaFileContent`. +:class:`~feincms.content.medialibrary.models.MediaFileContent`. It is generally recommended to use this hook to configure content types compared to putting the configuration into the site-wide settings file. This diff --git a/docs/medialibrary.rst b/docs/medialibrary.rst index 48f26887f..67dda6efb 100644 --- a/docs/medialibrary.rst +++ b/docs/medialibrary.rst @@ -19,7 +19,7 @@ add :mod:`feincms.module.medialibrary` to your ``INSTALLED_APPS`` setting, and create a content type for a media file as follows:: from feincms.module.page.models import Page - from feincms.content.medialibrary.v2 import MediaFileContent + from feincms.content.medialibrary.models import MediaFileContent Page.create_content_type(MediaFileContent, TYPE_CHOICES=( ('default', _('default')), diff --git a/feincms/content/medialibrary/models.py b/feincms/content/medialibrary/models.py index 43545662c..5a05080e2 100644 --- a/feincms/content/medialibrary/models.py +++ b/feincms/content/medialibrary/models.py @@ -26,7 +26,7 @@ class MediaFileContent(ContentWithMediaFile): Create a media file content as follows:: - from feincms.content.medialibrary.v2 import MediaFileContent + from feincms.content.medialibrary.models import MediaFileContent Page.create_content_type(MediaFileContent, TYPE_CHOICES=( ('default', _('Default')), ('lightbox', _('Lightbox')), From 133326f52c84e7271cabe7c315b4b80536186202 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Mon, 14 Dec 2015 15:44:21 +0100 Subject: [PATCH 332/654] Mark development version as such --- feincms/__init__.py | 2 +- setup.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 3214ca367..16d01c8d5 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 11, 3) +VERSION = (1, 11, 3, 'dev') __version__ = '.'.join(map(str, VERSION)) diff --git a/setup.py b/setup.py index 7de2162a6..5320bc2c8 100755 --- a/setup.py +++ b/setup.py @@ -10,10 +10,14 @@ def read(filename): with open(path, encoding='utf-8') as handle: return handle.read() +version = __import__('feincms').__version__ +devstatus = 'Development Status :: 5 - Production/Stable' +if '.dev' in version: + devstatus = 'Development Status :: 3 - Alpha' setup( name='FeinCMS', - version=__import__('feincms').__version__, + version=version, description='Django-based Page CMS and CMS building toolkit.', long_description=read('README.rst'), author='Matthias Kestenholz', @@ -45,7 +49,7 @@ def read(filename): 'pytz>=2014.10', ], classifiers=[ - 'Development Status :: 5 - Production/Stable', + devstatus, 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', From 169edd799f363ebaa67b566de7244bc837094cf6 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Mon, 14 Dec 2015 16:00:41 +0100 Subject: [PATCH 333/654] Bump for next version --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 16d01c8d5..e40dd5aba 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 11, 3, 'dev') +VERSION = (1, 11, 4, 'dev') __version__ = '.'.join(map(str, VERSION)) From d1eac6dac0801cddebf10296b6bfb8c84d85c78c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 17 Dec 2015 15:51:42 +0100 Subject: [PATCH 334/654] Fix #608: register_templates sets choices correctly with Django 1.9 --- feincms/models.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/feincms/models.py b/feincms/models.py index ecbb9a952..c86725ceb 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -402,7 +402,7 @@ def register_templates(cls, *templates): models.CharField(_('template'), max_length=255, choices=( # Dummy choice to trick Django. Cannot be empty, # otherwise admin.E023 happens. - ('base', 'base'), + ('__dummy', '__dummy'), )) ) field = next(iter( @@ -422,10 +422,17 @@ def _template(self): cls.template = property(_template) - cls.TEMPLATE_CHOICES = field._choices = [ + cls.TEMPLATE_CHOICES = [ (template_.key, template_.title,) for template_ in cls._feincms_templates.values() ] + try: + # noqa https://github.com/django/django/commit/80e3444eca045799cc40e50c92609e852a299d38 + # Django 1.9 uses this code + field.choices = cls.TEMPLATE_CHOICES + except AttributeError: + # Older versions of Django use that. + field._choices = cls.TEMPLATE_CHOICES field.default = cls.TEMPLATE_CHOICES[0][0] # Build a set of all regions used anywhere From 65be0af4a10bb00fcf1b357b2af5d4bc44bd0e8a Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 17 Dec 2015 16:10:26 +0100 Subject: [PATCH 335/654] FeinCMS v1.11.4 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 3214ca367..ea3f2640e 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 11, 3) +VERSION = (1, 11, 4) __version__ = '.'.join(map(str, VERSION)) From 02ee00c63ec7cba92dae1bfb02797721d0f278a1 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 22 Dec 2015 14:59:41 +0100 Subject: [PATCH 336/654] mptt 0.8 is Django 1.8 or better only --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index c3311d5b4..8eafe1842 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,20 +6,20 @@ python: - "3.2" - "3.4" env: - - REQ="Django>=1.6,<1.7 django-mptt" - - REQ="Django>=1.7,<1.8 django-mptt" + - REQ="Django>=1.6,<1.7 django-mptt<0.8" + - REQ="Django>=1.7,<1.8 django-mptt<0.8" - REQ="Django>=1.8,<1.9 django-mptt" - - REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" + - REQ="Django>=1.9,<1.10 django-mptt" matrix: exclude: - python: "2.6" - env: REQ="Django>=1.7,<1.8 django-mptt" + env: REQ="Django>=1.7,<1.8 django-mptt<0.8" - python: "2.6" env: REQ="Django>=1.8,<1.9 django-mptt" - python: "2.6" - env: REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" + env: REQ="Django>=1.9,<1.10 django-mptt" - python: "3.2" - env: REQ="Django>=1.9,<1.10 https://github.com/django-mptt/django-mptt/archive/master.zip" + env: REQ="Django>=1.9,<1.10 django-mptt" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install -q $REQ Pillow feedparser flake8 --use-mirrors From 306dc99627410336f8087ada72a603bfb6f8b5cc Mon Sep 17 00:00:00 2001 From: sperrygrove Date: Tue, 22 Dec 2015 11:03:58 +0000 Subject: [PATCH 337/654] Correct compatibility for django-reversion >= 1.10 --- feincms/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feincms/models.py b/feincms/models.py index c86725ceb..e740c886c 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -831,18 +831,18 @@ def replace_content_with(self, obj): @classmethod def register_with_reversion(cls): try: - from reversion import revisions as reversion + from reversion.revisions import register except ImportError: try: - import reversion + from reversion import register except ImportError: raise EnvironmentError("django-reversion is not installed") follow = [] for content_type in cls._feincms_content_types: follow.append('%s_set' % content_type.__name__.lower()) - reversion.register(content_type) - reversion.register(cls, follow=follow) + register(content_type) + register(cls, follow=follow) return Base From 49a5ec6e8f043d0d5df4f6a709a43ea2bd29c005 Mon Sep 17 00:00:00 2001 From: Fabian Germann Date: Wed, 23 Dec 2015 11:56:27 +0100 Subject: [PATCH 338/654] Pass config manually when registering CKE, otherwise the globally set format_tags take no effect --- feincms/templates/admin/content/richtext/init_ckeditor.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/templates/admin/content/richtext/init_ckeditor.html b/feincms/templates/admin/content/richtext/init_ckeditor.html index e92f6569f..d918c21f2 100644 --- a/feincms/templates/admin/content/richtext/init_ckeditor.html +++ b/feincms/templates/admin/content/richtext/init_ckeditor.html @@ -9,7 +9,7 @@ {% block config %} CKEDITOR.config.width = '787'; CKEDITOR.config.height= '300'; - // CKEDITOR.config.format_tags = 'p;h1;h2;h3;h4;pre'; + CKEDITOR.config.format_tags = 'p;h1;h2;h3;h4;pre'; CKEDITOR.config.toolbar = [ {% block toolbar %}['Maximize','-','Format','-','Bold','Italic','Underline','Strike','-','Subscript','Superscript','-','NumberedList','BulletedList','-','Anchor', 'Link','Unlink','-','Source']{% endblock %} ]; @@ -40,7 +40,7 @@ function feincms_richtext_add_ckeditor(field) { var id = field ? field.id : this.id; if (!(id in CKEDITOR.instances)) { - CKEDITOR.replace(id); + CKEDITOR.replace(id, CKEDITOR.config); } } From 3da926a4d1eac03a8ae6c3e13fc0dd861fc48238 Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Date: Tue, 29 Dec 2015 14:37:06 +0530 Subject: [PATCH 339/654] docs(readme): update "FeinCMS in a Box" url --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index e35e7ecdc..8762b52e7 100644 --- a/README.rst +++ b/README.rst @@ -80,7 +80,7 @@ Visit these sites Try out FeinCMS in a Box ------------------------ -`FeinCMS in a Box `_ is a +`FeinCMS in a Box `_ is a prepackaged installation of FeinCMS with a few additional modules and a setup script. Try it out! From 3084a4de9c9e2dc74f302599b484e2c0aedd86f0 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 18 Jan 2016 12:47:25 +0100 Subject: [PATCH 340/654] Fix the breadcrumbs navigation customization (Django 1.9 change URL changed) --- feincms/templates/admin/feincms/page/page/item_editor.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/feincms/templates/admin/feincms/page/page/item_editor.html b/feincms/templates/admin/feincms/page/page/item_editor.html index 67cb03f22..94ce86d22 100644 --- a/feincms/templates/admin/feincms/page/page/item_editor.html +++ b/feincms/templates/admin/feincms/page/page/item_editor.html @@ -16,14 +16,14 @@ {% block breadcrumbs %}
+ {{ form }} + + + + +
 
+ + diff --git a/feincms/templates/content/contactform/thanks.html b/feincms/templates/content/contactform/thanks.html new file mode 100644 index 000000000..7ea197009 --- /dev/null +++ b/feincms/templates/content/contactform/thanks.html @@ -0,0 +1,4 @@ +{% load i18n %} +
+

{% trans "Thanks!" %}

+
diff --git a/feincms/templates/content/file/default.html b/feincms/templates/content/file/default.html new file mode 100644 index 000000000..2f1d5baf0 --- /dev/null +++ b/feincms/templates/content/file/default.html @@ -0,0 +1,4 @@ + + {{ content.title }} + ({{ content.file.size|filesizeformat }}) + diff --git a/feincms/templates/content/image/default.html b/feincms/templates/content/image/default.html new file mode 100644 index 000000000..2df317749 --- /dev/null +++ b/feincms/templates/content/image/default.html @@ -0,0 +1 @@ +
{{ content.alt_text }}{% if content.caption %}
{{ content.caption }}
{% endif %}
diff --git a/feincms/templates/content/section/default.html b/feincms/templates/content/section/default.html new file mode 100644 index 000000000..c55ccee9f --- /dev/null +++ b/feincms/templates/content/section/default.html @@ -0,0 +1,7 @@ +

{{ content.title }}

+ +{% if content.mediafile %} + {{ content.mediafile }} +{% endif %} + +{{ content.richtext|safe }} diff --git a/feincms/templates/content/section/image.html b/feincms/templates/content/section/image.html new file mode 100644 index 000000000..396a3bbe1 --- /dev/null +++ b/feincms/templates/content/section/image.html @@ -0,0 +1,5 @@ +

{{ content.title }}

+ + + +{{ content.richtext|safe }} diff --git a/feincms/templates/content/video/sf.html b/feincms/templates/content/video/sf.html new file mode 100644 index 000000000..168c4183a --- /dev/null +++ b/feincms/templates/content/video/sf.html @@ -0,0 +1,7 @@ + + + + + diff --git a/feincms/templates/content/video/unknown.html b/feincms/templates/content/video/unknown.html new file mode 100644 index 000000000..832f1e050 --- /dev/null +++ b/feincms/templates/content/video/unknown.html @@ -0,0 +1 @@ +{{ content.video }} diff --git a/feincms/templates/content/video/vimeo.html b/feincms/templates/content/video/vimeo.html new file mode 100644 index 000000000..9ec14ad3c --- /dev/null +++ b/feincms/templates/content/video/vimeo.html @@ -0,0 +1,2 @@ + diff --git a/feincms/templates/content/video/youtube.html b/feincms/templates/content/video/youtube.html new file mode 100644 index 000000000..2b5fec51c --- /dev/null +++ b/feincms/templates/content/video/youtube.html @@ -0,0 +1,6 @@ + + + + + diff --git a/feincms/views.py b/feincms/views/__init__.py similarity index 100% rename from feincms/views.py rename to feincms/views/__init__.py diff --git a/feincms/views/decorators.py b/feincms/views/decorators.py new file mode 100644 index 000000000..365761807 --- /dev/null +++ b/feincms/views/decorators.py @@ -0,0 +1,10 @@ +# flake8: noqa +from __future__ import absolute_import, unicode_literals + +import warnings + +from feincms.apps import * + +warnings.warn( + 'Import ApplicationContent and friends from feincms.apps.', + DeprecationWarning, stacklevel=2) From 7ba02535d2ce042ef4eb63fd622911553413f7d3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 15:21:53 +0100 Subject: [PATCH 347/654] That is actually incorrect --- docs/releases/1.12.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst index b66f3e6db..7169e784b 100644 --- a/docs/releases/1.12.rst +++ b/docs/releases/1.12.rst @@ -65,8 +65,8 @@ Notable features and improvements * Rich text cleaning using Tidy has been removed. -* FeinCMS no longer comes with its own copy of jQuery — it reuses Django's - jQuery instead. +* ``FEINCMS_JQUERY_NO_CONFLICT`` is gone. Either use ``django.jQuery`` or + ``feincms.jQuery`` explicitly. * Some support has been added for ``django-filer``. From 80cc962fa7fdf8f5909cd2687fec93ef9b739b01 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 15:28:36 +0100 Subject: [PATCH 348/654] This is important --- docs/releases/1.12.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst index 7169e784b..0e74b79b3 100644 --- a/docs/releases/1.12.rst +++ b/docs/releases/1.12.rst @@ -31,6 +31,10 @@ The blog module has been completely removed ============================================ +Caching of pages in various page manager methods has been removed +================================================================= + + Backwards-incompatible changes ============================== From daf78990ba74ac3df2daf04251cb0fea1fbc1e5c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 18 Jan 2016 12:47:25 +0100 Subject: [PATCH 349/654] Fix the breadcrumbs navigation customization (Django 1.9 change URL changed) --- feincms/templates/admin/feincms/page/page/item_editor.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/feincms/templates/admin/feincms/page/page/item_editor.html b/feincms/templates/admin/feincms/page/page/item_editor.html index 67cb03f22..94ce86d22 100644 --- a/feincms/templates/admin/feincms/page/page/item_editor.html +++ b/feincms/templates/admin/feincms/page/page/item_editor.html @@ -16,14 +16,14 @@ {% block breadcrumbs %} '); var panel = $(''); - panel.html(c); + var $elem = $(elem); + panel.append($elem.children('div')); + $elem.remove(); // Remove the rest panels.push(panel); }); option_wrapper.append('
'); - $('#extension_options').html(panels); + $('#extension_options').append(panels); create_tabbed('#extension_options_wrapper', '#extension_options'); /* Done morphing extension options into tabs */ From 433683f273e6a5874fa7d33bc8fe888141b9ef61 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 16:04:16 +0100 Subject: [PATCH 351/654] Add a missing __init__ file --- feincms/content/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 feincms/content/__init__.py diff --git a/feincms/content/__init__.py b/feincms/content/__init__.py new file mode 100644 index 000000000..e69de29bb From 9ab82b232e23f749a13bcf5779e49392c54f41b1 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 16:12:26 +0100 Subject: [PATCH 352/654] Add a note about TemplateContent migration to the release notes --- docs/releases/1.12.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst index 0e74b79b3..9650e473c 100644 --- a/docs/releases/1.12.rst +++ b/docs/releases/1.12.rst @@ -26,6 +26,25 @@ be explicitly specified when creating the content type:: ('base.html', 'makes no sense'), ]) +Also, you need to add a model migration which renames the old +``filename`` field to the new ``template`` field:: + + # -*- coding: utf-8 -*- + from __future__ import unicode_literals + + from django.db import models, migrations + + + class Migration(migrations.Migration): + + dependencies = [ + ('page', 'WHATEVER IS APPROPRIATE'), + ] + + operations = [ + migrations.RenameField('TemplateContent', 'filename', 'template'), + ] + The blog module has been completely removed ============================================ From 94ffa872ac0a3addca8a8f8b26860b96f66826bd Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 16:36:36 +0100 Subject: [PATCH 353/654] Go through the docs once --- docs/contenttypes.rst | 48 ++++++++--------------------- docs/deprecation.rst | 6 ++++ docs/faq.rst | 25 --------------- docs/installation.rst | 4 +-- docs/integration.rst | 71 +++++++++++++++++++++---------------------- docs/medialibrary.rst | 37 +++++++++++----------- docs/page.rst | 50 ++++++++++++++++-------------- 7 files changed, 102 insertions(+), 139 deletions(-) diff --git a/docs/contenttypes.rst b/docs/contenttypes.rst index 2e5d61440..dbb365fd2 100644 --- a/docs/contenttypes.rst +++ b/docs/contenttypes.rst @@ -307,21 +307,12 @@ Bundled content types Application content ------------------- -.. module:: feincms.content.application.models +.. module:: feincms.apps .. class:: ApplicationContent() Used to let the administrator freely integrate 3rd party applications into the CMS. Described in :ref:`integration-applicationcontent`. - -Comments content ----------------- -.. module:: feincms.content.comments.models -.. class:: CommentsContent() - -Comment list and form using ``django.contrib.comments``. - - Contact form content -------------------- .. module:: feincms.content.contactform.models @@ -354,7 +345,7 @@ Additional arguments for :func:`~feincms.models.Base.create_content_type`: Media library integration ------------------------- -.. module:: feincms.content.medialibrary.models +.. module:: feincms.module.medialibrary.contents .. class:: MediaFileContent() Mini-framework for arbitrary file types with customizable rendering @@ -392,7 +383,7 @@ Additional arguments for :func:`~feincms.models.Base.create_content_type`: Raw content ----------- -.. module:: feincms.content.raw.models +.. module:: feincms.contents .. class:: RawContent() Raw HTML code, f.e. for flash movies or javascript code. @@ -400,7 +391,7 @@ Raw HTML code, f.e. for flash movies or javascript code. Rich text --------- -.. module:: feincms.content.richtext.models +.. module:: feincms.contents .. class:: RichTextContent() Rich text editor widget, stripped down to the essentials; no media support, @@ -458,18 +449,6 @@ To perform those operations ``contentblock_move_handlers.poorify`` -RSS feeds ---------- -.. module:: feincms.content.rss.models -.. class:: RSSContent - -A feed reader widget. This also serves as an example how to build a content -type that needs additional processing, in this case from a cron job. If an -RSS feed has been added to the CMS, ``manage.py update_rsscontent`` should -be run periodically (either through a cron job or through other means) to -keep the shown content up to date. The `feedparser` module is required. - - Section content --------------- .. module:: feincms.content.section.models @@ -480,17 +459,16 @@ Combined rich text editor, title and media file. Template content ---------------- -.. module:: feincms.content.template.models +.. module:: feincms.contents .. class:: TemplateContent() -This is a content type that just includes a snippet from a template. -This content type scans all template directories for templates below -``content/template/`` and allows the user to select one of these templates -which are then rendered using the Django template language. +This is a content type that just renders a template. The available +templates have to be specified when creating the content type:: -Note that some file extensions are automatically filtered so they will not -appear in the list, namely any filenames ending with ``.~`` or ``.tmp`` will -be ignored. + Page.create_content_type(TemplateContent, TEMPLATES=( + ('content/template/something1.html', _('Something 1')), + ('content/template/something2.html', _('Something 2')), + )) Also note that a template content is not sandboxed or specially rendered. Whatever a django template can do a TemplateContent snippet can do too, @@ -575,13 +553,13 @@ You could take advantage of the fact that ``create_content_type`` returns the created model:: from feincms.module.page.models import Page - from feincms.content.raw.models import RawContent + from feincms.contents import RawContent PageRawContent = Page.create_content_type(RawContent) Or you could use :func:`content_type_for`:: - from feincms.content.raw.models import RawContent + from feincms.contents import RawContent PageRawContent = Page.content_type_for(RawContent) diff --git a/docs/deprecation.rst b/docs/deprecation.rst index 7b82e7f9f..9bc7fb74e 100644 --- a/docs/deprecation.rst +++ b/docs/deprecation.rst @@ -120,3 +120,9 @@ No deprecations. * ``Page.cache_key`` has never been used by FeinCMS itself and will therefore be removed in a future release. Comparable functionality has been available for a long time with ``Page.path_to_cache_key``. + + +1.12 +==== + +* TODO update this \ No newline at end of file diff --git a/docs/faq.rst b/docs/faq.rst index f019067ab..e6f323788 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -30,28 +30,3 @@ too big, it might be time to reconsider whether you really want to use the extension mechanism or if it might not be easier to start freshly, only using the editor admin classes, feincms.models.Base and maybe parts of the included PageManager... - - - -I run ``syncdb`` and get a message about missing columns in the page table -========================================================================== - -You enabled the page module (added :mod:`feincms.module.page` to -``INSTALLED_APPS``), run syncdb, and afterwards registered a few -extensions. The extensions you activated -(:mod:`~feincms.module.page.extensions.datepublisher` and -:mod:`~feincms.module.page.extensions.translations`) add new fields to -the page model, but your first ``syncdb`` did not know about them and -therefore did not create the columns for those extensions. - -You can either remove the line ``Page.register_extensions(...)`` from -your code or drop the page_page table and re-run ``syncdb``. If you want -to keep the pages you've already created, you need to figure out the -correct ALTER TABLE statements for your database yourself. - - - -Is FeinCMS version X compatible with Django version Y? -====================================================== - -Check out the compatibility matrix `here `_. diff --git a/docs/installation.rst b/docs/installation.rst index 7c29615e1..d6ed76246 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,8 +9,8 @@ Installation This document describes the steps needed to install FeinCMS. -FeinCMS requires a working installation of Django_ version 1.4, 1.5, 1.6 or -1.7. See the Django_ documentation for how to install and configure Django. +FeinCMS requires a working installation of Django_ version 1.7 or better +See the Django_ documentation for how to install and configure Django. You can download a stable release of FeinCMS using ``pip``. Pip will install feincms and its dependencies. Dependencies which are automatically installed diff --git a/docs/integration.rst b/docs/integration.rst index ad536471f..c3a3b3eb4 100644 --- a/docs/integration.rst +++ b/docs/integration.rst @@ -15,19 +15,19 @@ The default CMS handler view is ``feincms.views.cbv.handler``. You can add the following as last line in your ``urls.py`` to make a catch-all for any pages which were not matched before:: - from feincms.views.cbv.views import Handler + from feincms.views import Handler handler = Handler.as_view() - urlpatterns += patterns('', + urlpatterns += [ url(r'^$', handler, name='feincms_home'), url(r'^(.*)/$', handler, name='feincms_handler'), - ) + ] Note that this default handler can also take a keyword parameter ``path`` to specify which url to render. You can use that functionality to implement a default page by adding another entry to your ``urls.py``:: - from feincms.views.cbv.views import Handler + from feincms.views import Handler handler = Handler.as_view() ... @@ -41,9 +41,9 @@ of your own URL patterns like this:: # ... - urlpatterns += patterns('', + urlpatterns += [ url(r'', include('feincms.urls')), - ) + ] The URLconf entry names ``feincms_home`` and ``feincms_handler`` must both exist somewhere in your project. The standard ``feincms.urls`` @@ -95,20 +95,20 @@ can be too easily violated. An example ``urls.py`` follows:: - from django.conf.urls import patterns, include, url + from django.conf.urls import include, url from django.views.generic.detail import DetailView from django.views.generic.list import ListView from news.models import Entry - urlpatterns = patterns('', + urlpatterns = [ url(r'^$', ListView.as_view( queryset=Entry.objects.all(), ), name='entry_list'), url(r'^(?P[^/]+)/$', DetailView.as_view( queryset=Entry.objects.all(), ), name='entry_detail'), - ) + ] Please note that you should not add the ``news/`` prefix here. You should *not* reference this ``urls.py`` file anywhere in a ``include`` statement. @@ -124,7 +124,7 @@ It's as simple as that:: Page.create_content_type(ApplicationContent, APPLICATIONS=( ('news.urls', 'News application'), - )) + )) Writing the models @@ -135,12 +135,11 @@ reachable through standard means (remember, they aren't ``include``\d anywhere) it's not possible to use standard ``reverse`` calls to determine the absolute URL of a news entry. FeinCMS provides its own ``app_reverse`` function (see :ref:`integration-reversing-urls` for -details) and ``permalink`` decorator mimicking the interface of -Django's standard functionality:: +details) mimicking the interface of Django's standard functionality:: from django.db import models - from feincms.content.application import models as app_models + from feincms.apps import app_reverse class Entry(models.Model): title = models.CharField(max_length=200) @@ -153,26 +152,22 @@ Django's standard functionality:: def __str__(self): return self.title - @app_models.permalink def get_absolute_url(self): - return ('entry_detail', 'news.urls', (), { + return app_reverse('entry_detail', 'news.urls', kwargs={ 'slug': self.slug, - }) + }) The only difference is that you do not only have to specify the view name -(``entry_detail``) but also the URLconf file (``news.urls``) for this -specific ``permalink`` decorator. The URLconf string must correspond to the -specification used in the ``APPLICATIONS`` list in the ``create_content_type`` -call. +(``entry_detail``) but also the URLconf file (``news.urls``). The URLconf +string must correspond to the specification used in the ``APPLICATIONS`` +list in the ``create_content_type`` call. .. note:: - Previous FeinCMS versions only provided a monkey patched ``reverse`` + Old FeinCMS versions only provided a monkey patched ``reverse`` method with a slightly different syntax for reversing URLs. This - behavior is still available and as of now (FeinCMS 1.5) still active - by default. It is recommended to start using the new way right now - and add ``FEINCMS_REVERSE_MONKEY_PATCH = False`` to your settings file. + behavior has been removed some time ago. Returning content from views @@ -246,12 +241,14 @@ of any template rendering calls: ``urls.py``:: - from django.conf.urls import patterns, include, url + from django.conf.urls import url - urlpatterns = patterns('news.views', - url(r'^$', 'entry_list', name='entry_list'), - url(r'^(?P[^/]+)/$', 'entry_detail', name='entry_detail'), - ) + from news.views import entry_list, entry_detail + + urlpatterns = [ + url(r'^$', entry_list, name='entry_list'), + url(r'^(?P[^/]+)/$', entry_detail, name='entry_detail'), + ] The two templates referenced, ``news/entry_list.html`` and @@ -290,7 +287,7 @@ that it resolves URLs from application contents. The second argument, ``urlconf``, has to correspond to the URLconf parameter passed in the ``APPLICATIONS`` list to ``Page.create_content_type``:: - from feincms.content.application.models import app_reverse + from feincms.apps import app_reverse app_reverse('mymodel-detail', 'myapp.urls', args=...) or:: @@ -344,8 +341,8 @@ need them. All of these must be specified in the ``APPLICATIONS`` argument to Page.create_content_type(ApplicationContent, APPLICATIONS=( ('registration', 'Account creation and management', { 'urls': 'yourapp.registration_urls', - }), - ) + }), + ) * ``admin_fields``: Adding more fields to the application content interface: @@ -362,15 +359,15 @@ need them. All of these must be specified in the ``APPLICATIONS`` argument to required=False, initial=form.instance.parameters.get('exclusive_subpages', True), help_text=_('Exclude everything other than the application\'s content when rendering subpages.'), - ), - } + ), + } Page.create_content_type(ApplicationContent, APPLICATIONS=( ('registration', 'Account creation and management', { 'urls': 'yourapp.registration_urls', 'admin_fields': registration_admin_fields, - }), - ) + }), + ) The form fields will only be visible after saving the ``ApplicationContent`` for the first time. They are stored inside a JSON-encoded field. The values @@ -448,7 +445,7 @@ You don't need to do anything else as long as you use the built-in yield navigation.PagePretender( title=category.name, url=category.get_absolute_url(), - ) + ) class PassthroughExtension(navigation.NavigationExtension): name = 'passthrough extension' diff --git a/docs/medialibrary.rst b/docs/medialibrary.rst index 67dda6efb..1f81ce596 100644 --- a/docs/medialibrary.rst +++ b/docs/medialibrary.rst @@ -22,9 +22,9 @@ create a content type for a media file as follows:: from feincms.content.medialibrary.models import MediaFileContent Page.create_content_type(MediaFileContent, TYPE_CHOICES=( - ('default', _('default')), - ('lightbox', _('lightbox')), - )) + ('default', _('default')), + ('lightbox', _('lightbox')), + )) ``TYPE_CHOICES`` has nothing to do with file types -- it's about choosing @@ -59,19 +59,19 @@ find a template for rendering the The default set of pre-defined content types and recognition functions is:: MediaFileBase.register_filetypes( - ('image', _('Image'), lambda f: re.compile(r'\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$', re.IGNORECASE).search(f)), - ('video', _('Video'), lambda f: re.compile(r'\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv)$', re.IGNORECASE).search(f)), - ('audio', _('Audio'), lambda f: re.compile(r'\.(au|mp3|m4a|wma|oga|ram|wav)$', re.IGNORECASE).search(f)), - ('pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf')), - ('swf', _('Flash'), lambda f: f.lower().endswith('.swf')), - ('txt', _('Text'), lambda f: f.lower().endswith('.txt')), - ('rtf', _('Rich Text'), lambda f: f.lower().endswith('.rtf')), - ('zip', _('Zip archive'), lambda f: f.lower().endswith('.zip')), - ('doc', _('Microsoft Word'), lambda f: re.compile(r'\.docx?$', re.IGNORECASE).search(f)), - ('xls', _('Microsoft Excel'), lambda f: re.compile(r'\.xlsx?$', re.IGNORECASE).search(f)), - ('ppt', _('Microsoft PowerPoint'), lambda f: re.compile(r'\.pptx?$', re.IGNORECASE).search(f)), - ('other', _('Binary'), lambda f: True), # Must be last - ) + ('image', _('Image'), lambda f: re.compile(r'\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$', re.IGNORECASE).search(f)), + ('video', _('Video'), lambda f: re.compile(r'\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv)$', re.IGNORECASE).search(f)), + ('audio', _('Audio'), lambda f: re.compile(r'\.(au|mp3|m4a|wma|oga|ram|wav)$', re.IGNORECASE).search(f)), + ('pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf')), + ('swf', _('Flash'), lambda f: f.lower().endswith('.swf')), + ('txt', _('Text'), lambda f: f.lower().endswith('.txt')), + ('rtf', _('Rich Text'), lambda f: f.lower().endswith('.rtf')), + ('zip', _('Zip archive'), lambda f: f.lower().endswith('.zip')), + ('doc', _('Microsoft Word'), lambda f: re.compile(r'\.docx?$', re.IGNORECASE).search(f)), + ('xls', _('Microsoft Excel'), lambda f: re.compile(r'\.xlsx?$', re.IGNORECASE).search(f)), + ('ppt', _('Microsoft PowerPoint'), lambda f: re.compile(r'\.pptx?$', re.IGNORECASE).search(f)), + ('other', _('Binary'), lambda f: True), # Must be last + ) You can add to that set by calling ``MediaFile.register_filetypes()`` with your new file types similar to the above. @@ -115,8 +115,9 @@ To have a thumbnail preview in your ModelAdmin and Inline class:: class ImageForProject(models.Model): project = models.ForeignKey(Project) - mediafile = MediaFileForeignKey(MediaFile, related_name='+', - limit_choices_to={'type': 'image'}) + mediafile = MediaFileForeignKey( + MediaFile, related_name='+', + limit_choices_to={'type': 'image'}) For the maginfying-glass select widget in your content type inherit your inline diff --git a/docs/page.rst b/docs/page.rst index 5ddd3f24e..4c9a35ff5 100644 --- a/docs/page.rst +++ b/docs/page.rst @@ -21,7 +21,8 @@ To activate the page module, you need to follow the instructions in :ref:`installation` and afterwards add :mod:`feincms.module.page` to your :data:`INSTALLED_APPS`. -Before proceeding with ``manage.py syncdb``, it might be a good idea to take +Before proceeding with ``manage.py makemigrations`` and ``./manage.py migrate``, +it might be a good idea to take a look at :ref:`page-extensions` -- the page module does have the minimum of features in the default configuration and you will probably want to enable several extensions. @@ -36,13 +37,13 @@ by adding the following lines somewhere into your project, for example in a from django.utils.translation import ugettext_lazy as _ from feincms.module.page.models import Page - from feincms.content.richtext.models import RichTextContent - from feincms.content.medialibrary.models import MediaFileContent + from feincms.contents import RichTextContent + from feincms.module.medialibrary.contents import MediaFileContent Page.register_extensions( - 'feincms.module.extensions.datepublisher', - 'feincms.module.extensions.translations' - ) # Example set of extensions + 'feincms.extensions.datepublisher', + 'feincms.extensions.translations' + ) # Example set of extensions Page.register_templates({ 'title': _('Standard template'), @@ -84,6 +85,11 @@ richtext support:: 'TINYMCE_JS_URL': STATIC_URL + 'your_custom_path/tiny_mce.js', } +If you want to use a different admin site, or want to apply customizations to +the admin class used, add the following setting to your site-wide settings:: + + FEINCMS_USE_PAGE_ADMIN = False + Wiring up the views =================== @@ -92,9 +98,9 @@ Just add the following lines to your ``urls.py`` to get a catch-all URL pattern: :: - urlpatterns += patterns('', + urlpatterns += [ url(r'', include('feincms.urls')), - ) + ] If you want to define a page as home page for the whole site, you can give it @@ -159,37 +165,38 @@ upon registering the extension. The :func:`register` method receives the :class:`~feincms.module.page.modeladmins.PageAdmin` as arguments. The extensions can be activated as follows:: - Page.register_extensions('feincms.module.page.extensions.navigation', - 'feincms.module.page.extensions.titles', - 'feincms.module.extensions.translations') + Page.register_extensions( + 'feincms.module.page.extensions.navigation', + 'feincms.module.page.extensions.titles', + 'feincms.extensions.translations') The following extensions are available currently: -* :mod:`feincms.module.extensions.changedate` --- Creation and modification dates +* :mod:`feincms.extensions.changedate` --- Creation and modification dates Adds automatically maintained creation and modification date fields to the page. -* :mod:`feincms.module.extensions.ct_tracker` --- Content type cache +* :mod:`feincms.extensions.ct_tracker` --- Content type cache Helps reduce database queries if you have three or more content types. -* :mod:`feincms.module.extensions.datepublisher` --- Date-based publishing +* :mod:`feincms.extensions.datepublisher` --- Date-based publishing Adds publication date and end date fields to the page, thereby enabling the administrator to define a date range where a page will be available to website visitors. -* :mod:`feincms.module.page.extensions.excerpt` --- Page summary +* :mod:`feincms.page.extensions.excerpt` --- Page summary Add a brief excerpt summarizing the content of this page. -* :mod:`feincms.module.extensions.featured` --- Simple featured flag for a page +* :mod:`feincms.extensions.featured` --- Simple featured flag for a page Lets administrators set a featured flag that lets you treat that page special. @@ -214,7 +221,7 @@ The following extensions are available currently: Add a many-to-many relationship field to relate this page to other pages. -* :mod:`feincms.module.extensions.seo` --- Search engine optimization +* :mod:`feincms.extensions.seo` --- Search engine optimization Adds fields to the page relevant for search engine optimization (SEO), currently only meta keywords and description. @@ -240,7 +247,7 @@ The following extensions are available currently: content area. -* :mod:`feincms.module.extensions.translations` --- Page translations +* :mod:`feincms.extensions.translations` --- Page translations Adds a language field and a recursive translations many to many field to the page, so that you can define the language the page is in and assign @@ -262,8 +269,7 @@ The following extensions are available currently: .. note:: These extension modules add new fields to the ``Page`` class. If you add or - remove page extensions after you've run ``syncdb`` for the first time you - have to change the database schema yourself, or use :ref:`migrations`. + remove page extensions you make and apply new migrations. Using page request processors @@ -379,10 +385,10 @@ Feincms site, add the following to your top-level urls.py:: from feincms.module.page.sitemap import PageSitemap sitemaps = {'pages' : PageSitemap} - urlpatterns += patterns('', + urlpatterns += [ url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}), - ) + ] This will produce a default sitemap at the /sitemap.xml url. A sitemap can be further customised by passing it appropriate parameters, like so:: From 08b86de557e5710e9a0841a72a68890cc50335cc Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 17:01:12 +0100 Subject: [PATCH 354/654] Fix #602: Add a note that when using ct_tracker, simply adding content blocks is not enough --- docs/page.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/page.rst b/docs/page.rst index 4c9a35ff5..e54a460e6 100644 --- a/docs/page.rst +++ b/docs/page.rst @@ -181,7 +181,11 @@ The following extensions are available currently: * :mod:`feincms.extensions.ct_tracker` --- Content type cache - Helps reduce database queries if you have three or more content types. + Helps reduce database queries if you have three or more content types by + caching in the database which content types are available on each page. + If this extension is used, ``Page._ct_inventory`` has to be nullified + after adding and/or removing content blocks, otherwise changes might not + be visible in the frontend. Saving the page instance accomplishes this. * :mod:`feincms.extensions.datepublisher` --- Date-based publishing From e38bffcb423d74a8f784993f260ce333be036795 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 17:06:04 +0100 Subject: [PATCH 355/654] makemigrations/migrate are a requirement --- docs/migrations.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/migrations.rst b/docs/migrations.rst index 6680c765a..ce17272c3 100644 --- a/docs/migrations.rst +++ b/docs/migrations.rst @@ -12,7 +12,11 @@ add migrations for FeinCMS models yourself inside your project. Django's builtin migrations =========================== -* Create a new folder in your app with an empty ``__init__.py`` inside. +This guide assumes that you are using both the page and the medialibrary +module from FeinCMS. Simply leave out medialibrary if unused. + +* Create a new folder named ``migrate`` in your app with an empty + ``__init__.py`` inside. * Add the following configuration to your ``settings.py``:: MIGRATION_MODULES = { @@ -24,3 +28,9 @@ Django's builtin migrations You **must not** use ``migrations`` as folder name for the FeinCMS migrations, otherwise Django **will** get confused. + +* Create initial migrations and apply them:: + + ./manage.py makemigrations medialibrary + ./manage.py makemigrations page + ./manage.py migrate From dd6601aee255ea0d2615430f7db5f03b1ce3f7ae Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 17:20:51 +0100 Subject: [PATCH 356/654] Update AUTHORS --- .mailmap | 1 + AUTHORS | 118 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/.mailmap b/.mailmap index 4cd2bdad0..68466507e 100644 --- a/.mailmap +++ b/.mailmap @@ -13,3 +13,4 @@ Skylar Saveland Stephan Jaekel Simon Bächler Simon Bächler +Matthias Kestenholz diff --git a/AUTHORS b/AUTHORS index 2deabd630..5f6987dd0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,101 +6,107 @@ The authors of FeinCMS are: * Simon Meers * Bojan Mihelac * Simon Bächler -* Bjorn Post * Stephan Jaekel +* Bjorn Post * Julien Phalip * Daniel Renz -* Stefan Reinhard -* Simon Schürpf * Matt Dawson +* Simon Schürpf * Skylar Saveland +* Stefan Reinhard * Peter Schmidt * Marc Egli * Psyton * Simon Schmid * Greg Turner * Charlie Denton +* Maarten van Gompel (proycon) * Bjarni Thorisson * Greg Taylor -* Maarten van Gompel (proycon) -* Urs Breton * Jonas +* Urs Breton * Antoni Aloy * Julian Bez -* Afonso Fernández Nogueira * Vítor Figueiró -* Sander van Leeuwen -* Martin Mahner -* Toby White +* Fabian Germann * Marc Tamlyn -* Nico Echaniz +* Martin Mahner * Max Peterson -* adsworth +* Nico Echaniz +* Sander van Leeuwen +* Toby White +* Afonso Fernández Nogueira * Brian Macdonald -* Gabriel Kovacs -* Maarten Draijer -* Andrew D. Ball -* Emmanuelle Delescolle * Torkn +* Emmanuelle Delescolle +* adsworth +* Maarten Draijer +* Gabriel Kovacs * Eric Delord -* tayg -* Cellarosi Marco -* Denis Popov -* Fabian Germann -* Fabian Vogler -* Håvard Grimelid +* Andrew D. Ball +* Michael Kutý +* Mikhail Korobov +* valmynd +* Wil Tan * Maciek Szczesniak * Marco Fucci -* Michael Bashkirov -* Mikhail Korobov * Perry Roper +* Denis Popov +* Cellarosi Marco * Raphael Jasjukaitis +* Håvard Grimelid * Richard A +* Michael Bashkirov +* tayg +* Fabian Vogler * Vaclav Klecanda -* Wil Tan -* Matthias K -* Jimmy Ye -* Alex Kamedov -* antiflu -* Mikkel Hoegh -* Jay Yu -* Olekasnadr Gula -* Paul Garner -* feczo -* Harro van der Klauw -* Piet Delport -* Gwildor Sok -* ilmarsm * Riccardo Coroneo -* niklaushug * Richard Bolt * Rico Moorman -* Giorgos Logiotatidis +* svleeuwen +* Saurabh Kumar * Sebastian Hillig * Silvan Spross -* George Karpenkov -* Domas Lapinskas -* Denis Martinez -* David Evans -* Darryl Woods -* Daniele Procida * Artur Barseghyan -* Sumit Datta -* Sun Liwen -* Tobias Haffner -* Anshuman Bhaduri +* Harro van der Klauw * Andrin Heusser * Andrey Popelo -* svleeuwen -* Valtron * Andi Albrecht +* Alex Kamedov +* Sumit Datta +* Sun Liwen +* Tobias Haffner * Alen Mujezinovic +* Valtron * Wim Feijen -* Marco Cellarosi * Wouter van der Graaf -* Mark Renton -* Livio Lunin +* antiflu +* feczo +* George Karpenkov +* Giorgos Logiotatidis +* Erik Stein +* Gwildor Sok +* Anshuman Bhaduri +* Jay Yu +* Jimmy Ye +* Jonas Svensson +* Domas Lapinskas +* Kevin Etienne * Laurent Paoletti +* Livio Lunin +* Denis Martinez +* David Evans +* i-trofimtschuk +* Marco Cellarosi +* Mark Renton +* Darryl Woods +* ilmarsm * Mason Hugus -* Kevin Etienne -* Jonas Svensson +* Daniele Procida +* Dan Büschlen +* niklaushug +* Mikkel Hoegh +* sperrygrove +* Olekasnadr Gula +* Paul Garner +* Piet Delport From aee69fa98b6f578085ef3eb3f64f777223096657 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 15:58:55 +0100 Subject: [PATCH 357/654] Stop reevaluating inline scripts in collapsed admin fieldsets for no reason at all --- feincms/static/feincms/item_editor.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/feincms/static/feincms/item_editor.js b/feincms/static/feincms/item_editor.js index a7ddf71df..55920df96 100644 --- a/feincms/static/feincms/item_editor.js +++ b/feincms/static/feincms/item_editor.js @@ -377,11 +377,8 @@ options_fieldsets.each(function(idx, elem) { var option_title = $('h2', $(elem)).text(); - var c = $(elem).children('div'); var id_base = 'extension_option_'+ idx; - $(elem).remove(); - var paren = option_title.indexOf(' ('); if(paren > 0) option_title = option_title.substr(0, paren); @@ -390,12 +387,14 @@ option_title + '
'); var panel = $(''); - panel.html(c); + var $elem = $(elem); + panel.append($elem.children('div')); + $elem.remove(); // Remove the rest panels.push(panel); }); option_wrapper.append('
'); - $('#extension_options').html(panels); + $('#extension_options').append(panels); create_tabbed('#extension_options_wrapper', '#extension_options'); /* Done morphing extension options into tabs */ From 6ca79f5dafd60b818af279716ec1c067c34934e7 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 17:20:51 +0100 Subject: [PATCH 358/654] Update AUTHORS --- .mailmap | 1 + AUTHORS | 118 +++++++++++++++++++++++++++++-------------------------- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/.mailmap b/.mailmap index 4cd2bdad0..68466507e 100644 --- a/.mailmap +++ b/.mailmap @@ -13,3 +13,4 @@ Skylar Saveland Stephan Jaekel Simon Bächler Simon Bächler +Matthias Kestenholz diff --git a/AUTHORS b/AUTHORS index 2deabd630..5f6987dd0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,101 +6,107 @@ The authors of FeinCMS are: * Simon Meers * Bojan Mihelac * Simon Bächler -* Bjorn Post * Stephan Jaekel +* Bjorn Post * Julien Phalip * Daniel Renz -* Stefan Reinhard -* Simon Schürpf * Matt Dawson +* Simon Schürpf * Skylar Saveland +* Stefan Reinhard * Peter Schmidt * Marc Egli * Psyton * Simon Schmid * Greg Turner * Charlie Denton +* Maarten van Gompel (proycon) * Bjarni Thorisson * Greg Taylor -* Maarten van Gompel (proycon) -* Urs Breton * Jonas +* Urs Breton * Antoni Aloy * Julian Bez -* Afonso Fernández Nogueira * Vítor Figueiró -* Sander van Leeuwen -* Martin Mahner -* Toby White +* Fabian Germann * Marc Tamlyn -* Nico Echaniz +* Martin Mahner * Max Peterson -* adsworth +* Nico Echaniz +* Sander van Leeuwen +* Toby White +* Afonso Fernández Nogueira * Brian Macdonald -* Gabriel Kovacs -* Maarten Draijer -* Andrew D. Ball -* Emmanuelle Delescolle * Torkn +* Emmanuelle Delescolle +* adsworth +* Maarten Draijer +* Gabriel Kovacs * Eric Delord -* tayg -* Cellarosi Marco -* Denis Popov -* Fabian Germann -* Fabian Vogler -* Håvard Grimelid +* Andrew D. Ball +* Michael Kutý +* Mikhail Korobov +* valmynd +* Wil Tan * Maciek Szczesniak * Marco Fucci -* Michael Bashkirov -* Mikhail Korobov * Perry Roper +* Denis Popov +* Cellarosi Marco * Raphael Jasjukaitis +* Håvard Grimelid * Richard A +* Michael Bashkirov +* tayg +* Fabian Vogler * Vaclav Klecanda -* Wil Tan -* Matthias K -* Jimmy Ye -* Alex Kamedov -* antiflu -* Mikkel Hoegh -* Jay Yu -* Olekasnadr Gula -* Paul Garner -* feczo -* Harro van der Klauw -* Piet Delport -* Gwildor Sok -* ilmarsm * Riccardo Coroneo -* niklaushug * Richard Bolt * Rico Moorman -* Giorgos Logiotatidis +* svleeuwen +* Saurabh Kumar * Sebastian Hillig * Silvan Spross -* George Karpenkov -* Domas Lapinskas -* Denis Martinez -* David Evans -* Darryl Woods -* Daniele Procida * Artur Barseghyan -* Sumit Datta -* Sun Liwen -* Tobias Haffner -* Anshuman Bhaduri +* Harro van der Klauw * Andrin Heusser * Andrey Popelo -* svleeuwen -* Valtron * Andi Albrecht +* Alex Kamedov +* Sumit Datta +* Sun Liwen +* Tobias Haffner * Alen Mujezinovic +* Valtron * Wim Feijen -* Marco Cellarosi * Wouter van der Graaf -* Mark Renton -* Livio Lunin +* antiflu +* feczo +* George Karpenkov +* Giorgos Logiotatidis +* Erik Stein +* Gwildor Sok +* Anshuman Bhaduri +* Jay Yu +* Jimmy Ye +* Jonas Svensson +* Domas Lapinskas +* Kevin Etienne * Laurent Paoletti +* Livio Lunin +* Denis Martinez +* David Evans +* i-trofimtschuk +* Marco Cellarosi +* Mark Renton +* Darryl Woods +* ilmarsm * Mason Hugus -* Kevin Etienne -* Jonas Svensson +* Daniele Procida +* Dan Büschlen +* niklaushug +* Mikkel Hoegh +* sperrygrove +* Olekasnadr Gula +* Paul Garner +* Piet Delport From 3a857b29ad5b56adb70d69bae54db796944b1617 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 4 Feb 2016 17:01:12 +0100 Subject: [PATCH 359/654] Fix #602: Add a note that when using ct_tracker, simply adding content blocks is not enough --- docs/page.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/page.rst b/docs/page.rst index 5ddd3f24e..7ce79bc93 100644 --- a/docs/page.rst +++ b/docs/page.rst @@ -174,7 +174,11 @@ The following extensions are available currently: * :mod:`feincms.module.extensions.ct_tracker` --- Content type cache - Helps reduce database queries if you have three or more content types. + Helps reduce database queries if you have three or more content types by + caching in the database which content types are available on each page. + If this extension is used, ``Page._ct_inventory`` has to be nullified + after adding and/or removing content blocks, otherwise changes might not + be visible in the frontend. Saving the page instance accomplishes this. * :mod:`feincms.module.extensions.datepublisher` --- Date-based publishing From 0a52a9f40953313deef4d56e394adae5dd888e7a Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Feb 2016 14:32:37 +0100 Subject: [PATCH 360/654] Move contents back to feincms.content to minimize the diff Splitting up into packages may follow, but that would be something different. --- feincms/content/filer/__init__.py | 0 feincms/content/filer/models.py | 117 ++++++++++++++++ feincms/content/raw/models.py | 26 +++- feincms/content/richtext/models.py | 49 ++++++- feincms/content/template/models.py | 41 +++++- feincms/contents.py | 206 +---------------------------- 6 files changed, 219 insertions(+), 220 deletions(-) create mode 100644 feincms/content/filer/__init__.py create mode 100644 feincms/content/filer/models.py diff --git a/feincms/content/filer/__init__.py b/feincms/content/filer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py new file mode 100644 index 000000000..634dd4ba7 --- /dev/null +++ b/feincms/content/filer/models.py @@ -0,0 +1,117 @@ +from __future__ import absolute_import, unicode_literals + +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured +from django.db import models +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ + +from feincms.admin.item_editor import FeinCMSInline + +try: + from filer.fields.file import FilerFileField + from filer.fields.image import FilerImageField +except ImportError: + __all__ = () + +else: + + __all__ = ( + 'MediaFileContentInline', 'ContentWithFilerFile', + 'FilerFileContent', 'FilerImageContent', + ) + + class MediaFileContentInline(FeinCMSInline): + radio_fields = {'type': admin.VERTICAL} + + class ContentWithFilerFile(models.Model): + """ + File content + """ + feincms_item_editor_inline = MediaFileContentInline + + class Meta: + abstract = True + + def render(self, **kwargs): + ctx = {'content': self} + ctx.update(kwargs) + return render_to_string([ + 'content/filer/%s_%s.html' % (self.file_type, self.type), + 'content/filer/%s.html' % self.type, + 'content/filer/%s.html' % self.file_type, + 'content/filer/default.html', + ], ctx, context_instance=kwargs.get('context')) + + class FilerFileContent(ContentWithFilerFile): + mediafile = FilerFileField(verbose_name=_('file'), related_name='+') + file_type = 'file' + type = 'download' + + class Meta: + abstract = True + verbose_name = _('file') + verbose_name_plural = _('files') + + class FilerImageContent(ContentWithFilerFile): + """ + Create a media file content as follows:: + + from feincms.contents import FilerImageContent + Page.create_content_type(FilerImageContent, TYPE_CHOICES=( + ('inline', _('Default')), + ('lightbox', _('Lightbox')), + ('whatever', _('Whatever')), + )) + + For a media file of type 'image' and type 'lightbox', the following + templates are tried in order: + + * content/mediafile/image_lightbox.html + * content/mediafile/lightbox.html + * content/mediafile/image.html + * content/mediafile/default.html + + The context contains ``content`` and ``request`` (if available). + + The content.mediafile attribute are as follows (selection): + label, description, default_caption, default_alt_text, + author, must_always_publish_author_credit, + must_always_publish_copyright, date_taken, file, id, is_public, url + """ + + mediafile = FilerImageField(verbose_name=_('image'), related_name='+') + caption = models.CharField( + _('caption'), + max_length=1000, + blank=True, + ) + url = models.CharField( + _('URL'), + max_length=1000, + blank=True, + ) + + file_type = 'image' + + class Meta: + abstract = True + verbose_name = _('image') + verbose_name_plural = _('images') + + @classmethod + def initialize_type(cls, TYPE_CHOICES=None): + if TYPE_CHOICES is None: + raise ImproperlyConfigured( + 'You have to set TYPE_CHOICES when' + ' creating a %s' % cls.__name__) + + cls.add_to_class( + 'type', + models.CharField( + _('type'), + max_length=20, + choices=TYPE_CHOICES, + default=TYPE_CHOICES[0][0], + ), + ) diff --git a/feincms/content/raw/models.py b/feincms/content/raw/models.py index 7b23f8b74..487c5a241 100644 --- a/feincms/content/raw/models.py +++ b/feincms/content/raw/models.py @@ -1,10 +1,24 @@ -# flake8: noqa from __future__ import absolute_import, unicode_literals -import warnings +from django.db import models +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy as _ -from feincms.contents import RawContent -warnings.warn( - 'Import RawContent from feincms.contents.', - DeprecationWarning, stacklevel=2) +class RawContent(models.Model): + """ + Content type which can be used to input raw HTML code into the CMS. + + The content isn't escaped and can be used to insert CSS or JS + snippets too. + """ + + text = models.TextField(_('content'), blank=True) + + class Meta: + abstract = True + verbose_name = _('raw content') + verbose_name_plural = _('raw contents') + + def render(self, **kwargs): + return mark_safe(self.text) diff --git a/feincms/content/richtext/models.py b/feincms/content/richtext/models.py index 28f6b8481..e32ee4c09 100644 --- a/feincms/content/richtext/models.py +++ b/feincms/content/richtext/models.py @@ -1,10 +1,47 @@ -# flake8: noqa from __future__ import absolute_import, unicode_literals -import warnings +from django.db import models +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ -from feincms.contents import RichTextContent +from feincms import settings +from feincms.contrib.richtext import RichTextField -warnings.warn( - 'Import RichTextContent from feincms.contents.', - DeprecationWarning, stacklevel=2) + +class RichTextContent(models.Model): + """ + Rich text content. Uses TinyMCE by default, but can be configured to do + anything you want using ``FEINCMS_RICHTEXT_INIT_CONTEXT`` and + ``FEINCMS_RICHTEXT_INIT_TEMPLATE``. + + If you are using TinyMCE 4.x then ``FEINCMS_RICHTEXT_INIT_TEMPLATE`` + needs to be set to ``admin/content/richtext/init_tinymce4.html``. + + Optionally runs the HTML code through HTML cleaners if you specify + ``cleanse=True`` when calling ``create_content_type``. + """ + + feincms_item_editor_context_processors = ( + lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, + ) + feincms_item_editor_includes = { + 'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE], + } + + class Meta: + abstract = True + verbose_name = _('rich text') + verbose_name_plural = _('rich texts') + + def render(self, **kwargs): + return render_to_string( + 'content/richtext/default.html', + {'content': self}, + context_instance=kwargs.get('context')) + + @classmethod + def initialize_type(cls, cleanse=None): + cls.add_to_class( + 'text', + RichTextField(_('text'), blank=True, cleanse=cleanse), + ) diff --git a/feincms/content/template/models.py b/feincms/content/template/models.py index 0af3ce5b4..1060e1235 100644 --- a/feincms/content/template/models.py +++ b/feincms/content/template/models.py @@ -1,10 +1,39 @@ -# flake8: noqa from __future__ import absolute_import, unicode_literals -import warnings +from django.db import models +from django.template.loader import render_to_string +from django.utils.translation import ugettext_lazy as _ -from feincms.contents import TemplateContent +from feincms.content.raw.models import RawContent # noqa +from feincms.content.richtext.models import RichTextContent # noqa -warnings.warn( - 'Import TemplateContent from feincms.contents.', - DeprecationWarning, stacklevel=2) + +class TemplateContent(models.Model): + """ + Pass a list of templates when creating this content type. It uses the + default template system:: + + Page.create_content_type(TemplateContent, TEMPLATES=[ + ('content/template/something1.html', 'something'), + ('content/template/something2.html', 'something else'), + ('base.html', 'makes no sense'), + ]) + """ + class Meta: + abstract = True + verbose_name = _('template content') + verbose_name_plural = _('template contents') + + @classmethod + def initialize_type(cls, TEMPLATES): + cls.add_to_class('template', models.CharField( + _('template'), + max_length=100, + choices=TEMPLATES, + )) + + def render(self, **kwargs): + return render_to_string( + self.template, + {'content': self}, + context_instance=kwargs.get('context')) diff --git a/feincms/contents.py b/feincms/contents.py index 76f4253d9..9dc37f9a7 100644 --- a/feincms/contents.py +++ b/feincms/contents.py @@ -1,204 +1,6 @@ from __future__ import absolute_import, unicode_literals -from django.contrib import admin -from django.core.exceptions import ImproperlyConfigured -from django.db import models -from django.template.loader import render_to_string -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy as _ - -from feincms import settings -from feincms.admin.item_editor import FeinCMSInline -from feincms.contrib.richtext import RichTextField - - -class RawContent(models.Model): - """ - Content type which can be used to input raw HTML code into the CMS. - - The content isn't escaped and can be used to insert CSS or JS - snippets too. - """ - - text = models.TextField(_('content'), blank=True) - - class Meta: - abstract = True - verbose_name = _('raw content') - verbose_name_plural = _('raw contents') - - def render(self, **kwargs): - return mark_safe(self.text) - - -class RichTextContent(models.Model): - """ - Rich text content. Uses TinyMCE by default, but can be configured to do - anything you want using ``FEINCMS_RICHTEXT_INIT_CONTEXT`` and - ``FEINCMS_RICHTEXT_INIT_TEMPLATE``. - - If you are using TinyMCE 4.x then ``FEINCMS_RICHTEXT_INIT_TEMPLATE`` - needs to be set to ``admin/content/richtext/init_tinymce4.html``. - - Optionally runs the HTML code through HTML cleaners if you specify - ``cleanse=True`` when calling ``create_content_type``. - """ - - feincms_item_editor_context_processors = ( - lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, - ) - feincms_item_editor_includes = { - 'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE], - } - - class Meta: - abstract = True - verbose_name = _('rich text') - verbose_name_plural = _('rich texts') - - def render(self, **kwargs): - return render_to_string( - 'content/richtext/default.html', - {'content': self}, - context_instance=kwargs.get('context')) - - @classmethod - def initialize_type(cls, cleanse=None): - cls.add_to_class( - 'text', - RichTextField(_('text'), blank=True, cleanse=cleanse), - ) - - -class TemplateContent(models.Model): - """ - Pass a list of templates when creating this content type. It uses the - default template system:: - - Page.create_content_type(TemplateContent, TEMPLATES=[ - ('content/template/something1.html', 'something'), - ('content/template/something2.html', 'something else'), - ('base.html', 'makes no sense'), - ]) - """ - class Meta: - abstract = True - verbose_name = _('template content') - verbose_name_plural = _('template contents') - - @classmethod - def initialize_type(cls, TEMPLATES): - cls.add_to_class('template', models.CharField( - _('template'), - max_length=100, - choices=TEMPLATES, - )) - - def render(self, **kwargs): - return render_to_string( - self.template, - {'content': self}, - context_instance=kwargs.get('context')) - - -try: - from filer.fields.file import FilerFileField - from filer.fields.image import FilerImageField -except ImportError: - pass -else: - - class MediaFileContentInline(FeinCMSInline): - radio_fields = {'type': admin.VERTICAL} - - class ContentWithFilerFile(models.Model): - """ - File content - """ - feincms_item_editor_inline = MediaFileContentInline - - class Meta: - abstract = True - - def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string([ - 'content/filer/%s_%s.html' % (self.file_type, self.type), - 'content/filer/%s.html' % self.type, - 'content/filer/%s.html' % self.file_type, - 'content/filer/default.html', - ], ctx, context_instance=kwargs.get('context')) - - class FilerFileContent(ContentWithFilerFile): - mediafile = FilerFileField(verbose_name=_('file'), related_name='+') - file_type = 'file' - type = 'download' - - class Meta: - abstract = True - verbose_name = _('file') - verbose_name_plural = _('files') - - class FilerImageContent(ContentWithFilerFile): - """ - Create a media file content as follows:: - - from feincms.contents import FilerImageContent - Page.create_content_type(FilerImageContent, TYPE_CHOICES=( - ('inline', _('Default')), - ('lightbox', _('Lightbox')), - ('whatever', _('Whatever')), - )) - - For a media file of type 'image' and type 'lightbox', the following - templates are tried in order: - - * content/mediafile/image_lightbox.html - * content/mediafile/lightbox.html - * content/mediafile/image.html - * content/mediafile/default.html - - The context contains ``content`` and ``request`` (if available). - - The content.mediafile attribute are as follows (selection): - label, description, default_caption, default_alt_text, - author, must_always_publish_author_credit, - must_always_publish_copyright, date_taken, file, id, is_public, url - """ - - mediafile = FilerImageField(verbose_name=_('image'), related_name='+') - caption = models.CharField( - _('caption'), - max_length=1000, - blank=True, - ) - url = models.CharField( - _('URL'), - max_length=1000, - blank=True, - ) - - file_type = 'image' - - class Meta: - abstract = True - verbose_name = _('image') - verbose_name_plural = _('images') - - @classmethod - def initialize_type(cls, TYPE_CHOICES=None): - if TYPE_CHOICES is None: - raise ImproperlyConfigured( - 'You have to set TYPE_CHOICES when' - ' creating a %s' % cls.__name__) - - cls.add_to_class( - 'type', - models.CharField( - _('type'), - max_length=20, - choices=TYPE_CHOICES, - default=TYPE_CHOICES[0][0], - ), - ) +from feincms.content.filer.models import * # noqa +from feincms.content.raw.models import RawContent # noqa +from feincms.content.richtext.models import RichTextContent # noqa +from feincms.content.template.models import TemplateContent # noqa From 487eebe0aff84a5cb9d2fa27805d04991d9def92 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Feb 2016 14:41:55 +0100 Subject: [PATCH 361/654] Move application content back as well... --- feincms/apps.py | 3 + feincms/apps/__init__.py | 53 ---- feincms/apps/contents.py | 300 ------------------ feincms/apps/reverse.py | 98 ------ feincms/content/application/models.py | 441 +++++++++++++++++++++++++- 5 files changed, 438 insertions(+), 457 deletions(-) create mode 100644 feincms/apps.py delete mode 100644 feincms/apps/__init__.py delete mode 100644 feincms/apps/contents.py delete mode 100644 feincms/apps/reverse.py diff --git a/feincms/apps.py b/feincms/apps.py new file mode 100644 index 000000000..fa5e7ac8c --- /dev/null +++ b/feincms/apps.py @@ -0,0 +1,3 @@ +# flake8: noqa + +from feincms.content.application.models import * diff --git a/feincms/apps/__init__.py b/feincms/apps/__init__.py deleted file mode 100644 index 367587f94..000000000 --- a/feincms/apps/__init__.py +++ /dev/null @@ -1,53 +0,0 @@ -from __future__ import absolute_import - -from functools import wraps - -from django.http import HttpResponse -from django.template.response import TemplateResponse - -from .contents import ApplicationContent -from .reverse import app_reverse, app_reverse_lazy, permalink - - -__all__ = ( - 'ApplicationContent', - 'app_reverse', 'app_reverse_lazy', 'permalink', - 'UnpackTemplateResponse', 'standalone', 'unpack', -) - - -class UnpackTemplateResponse(TemplateResponse): - """ - Completely the same as marking applicationcontent-contained views with - the ``feincms.views.decorators.unpack`` decorator. - """ - _feincms_unpack = True - - -def standalone(view_func): - """ - Marks the view method as standalone view; this means that - ``HttpResponse`` objects returned from ``ApplicationContent`` - are returned directly, without further processing. - """ - - def inner(request, *args, **kwargs): - response = view_func(request, *args, **kwargs) - if isinstance(response, HttpResponse): - response.standalone = True - return response - return wraps(view_func)(inner) - - -def unpack(view_func): - """ - Marks the returned response as to-be-unpacked if it is a - ``TemplateResponse``. - """ - - def inner(request, *args, **kwargs): - response = view_func(request, *args, **kwargs) - if isinstance(response, TemplateResponse): - response._feincms_unpack = True - return response - return wraps(view_func)(inner) diff --git a/feincms/apps/contents.py b/feincms/apps/contents.py deleted file mode 100644 index 7e95a62b8..000000000 --- a/feincms/apps/contents.py +++ /dev/null @@ -1,300 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -from email.utils import parsedate -from functools import partial -from time import mktime - -from django.conf import settings -from django.core.urlresolvers import Resolver404, resolve -from django.db import models -from django.http import HttpResponse -from django.utils.http import http_date -from django.utils.safestring import mark_safe -from django.utils.translation import get_language, ugettext_lazy as _ - -from feincms.admin.item_editor import ItemEditorForm -from feincms.contrib.fields import JSONField -from feincms.translations import short_language_code -from feincms.utils import get_object - - -class ApplicationContent(models.Model): - #: parameters is used to serialize instance-specific data which will be - # provided to the view code. This allows customization (e.g. "Embed - # MyBlogApp for blog ") - parameters = JSONField(null=True, editable=False) - - ALL_APPS_CONFIG = {} - - class Meta: - abstract = True - verbose_name = _('application content') - verbose_name_plural = _('application contents') - - @classmethod - def initialize_type(cls, APPLICATIONS): - for i in APPLICATIONS: - if not 2 <= len(i) <= 3: - raise ValueError( - "APPLICATIONS must be provided with tuples containing at" - " least two parameters (urls, name) and an optional extra" - " config dict") - - urls, name = i[0:2] - - if len(i) == 3: - app_conf = i[2] - - if not isinstance(app_conf, dict): - raise ValueError( - "The third parameter of an APPLICATIONS entry must be" - " a dict or the name of one!") - else: - app_conf = {} - - cls.ALL_APPS_CONFIG[urls] = { - "urls": urls, - "name": name, - "config": app_conf - } - - cls.add_to_class( - 'urlconf_path', - models.CharField(_('application'), max_length=100, choices=[ - (c['urls'], c['name']) for c in cls.ALL_APPS_CONFIG.values()]) - ) - - class ApplicationContentItemEditorForm(ItemEditorForm): - app_config = {} - custom_fields = {} - - def __init__(self, *args, **kwargs): - super(ApplicationContentItemEditorForm, self).__init__( - *args, **kwargs) - - instance = kwargs.get("instance", None) - - if instance: - try: - # TODO use urlconf_path from POST if set - # urlconf_path = request.POST.get('...urlconf_path', - # instance.urlconf_path) - self.app_config = cls.ALL_APPS_CONFIG[ - instance.urlconf_path]['config'] - except KeyError: - self.app_config = {} - - self.custom_fields = {} - admin_fields = self.app_config.get('admin_fields', {}) - - if isinstance(admin_fields, dict): - self.custom_fields.update(admin_fields) - else: - get_fields = get_object(admin_fields) - self.custom_fields.update( - get_fields(self, *args, **kwargs)) - - params = self.instance.parameters - for k, v in self.custom_fields.items(): - v.initial = params.get(k) - self.fields[k] = v - if k in params: - self.fields[k].initial = params[k] - - def save(self, commit=True, *args, **kwargs): - # Django ModelForms return the model instance from save. We'll - # call save with commit=False first to do any necessary work & - # get the model so we can set .parameters to the values of our - # custom fields before calling save(commit=True) - - m = super(ApplicationContentItemEditorForm, self).save( - commit=False, *args, **kwargs) - - m.parameters = dict( - (k, self.cleaned_data[k]) - for k in self.custom_fields if k in self.cleaned_data) - - if commit: - m.save(**kwargs) - - return m - - # This provides hooks for us to customize the admin interface for - # embedded instances: - cls.feincms_item_editor_form = ApplicationContentItemEditorForm - - def __init__(self, *args, **kwargs): - super(ApplicationContent, self).__init__(*args, **kwargs) - self.app_config = self.ALL_APPS_CONFIG.get( - self.urlconf_path, {}).get('config', {}) - - def process(self, request, **kw): - page_url = self.parent.get_absolute_url() - - # Provide a way for appcontent items to customize URL processing by - # altering the perceived path of the page: - if "path_mapper" in self.app_config: - path_mapper = get_object(self.app_config["path_mapper"]) - path, page_url = path_mapper( - request.path, - page_url, - appcontent_parameters=self.parameters - ) - else: - path = request._feincms_extra_context['extra_path'] - - # Resolve the module holding the application urls. - urlconf_path = self.app_config.get('urls', self.urlconf_path) - - try: - fn, args, kwargs = resolve(path, urlconf_path) - except (ValueError, Resolver404): - raise Resolver404(str('Not found (resolving %r in %r failed)') % ( - path, urlconf_path)) - - # Variables from the ApplicationContent parameters are added to request - # so we can expose them to our templates via the appcontent_parameters - # context_processor - request._feincms_extra_context.update(self.parameters) - - # Save the application configuration for reuse elsewhere - request._feincms_extra_context.update({ - 'app_config': dict( - self.app_config, - urlconf_path=self.urlconf_path, - ), - }) - - view_wrapper = self.app_config.get("view_wrapper", None) - if view_wrapper: - fn = partial( - get_object(view_wrapper), - view=fn, - appcontent_parameters=self.parameters - ) - - output = fn(request, *args, **kwargs) - - if isinstance(output, HttpResponse): - if self.send_directly(request, output): - return output - elif output.status_code == 200: - - if self.unpack(request, output) and 'view' in kw: - # Handling of @unpack and UnpackTemplateResponse - kw['view'].template_name = output.template_name - kw['view'].request._feincms_extra_context.update( - output.context_data) - - else: - # If the response supports deferred rendering, render the - # response right now. We do not handle template response - # middleware. - if hasattr(output, 'render') and callable(output.render): - output.render() - - self.rendered_result = mark_safe( - output.content.decode('utf-8')) - - self.rendered_headers = {} - - # Copy relevant headers for later perusal - for h in ('Cache-Control', 'Last-Modified', 'Expires'): - if h in output: - self.rendered_headers.setdefault( - h, []).append(output[h]) - - elif isinstance(output, tuple) and 'view' in kw: - kw['view'].template_name = output[0] - kw['view'].request._feincms_extra_context.update(output[1]) - - else: - self.rendered_result = mark_safe(output) - - return True # successful - - def send_directly(self, request, response): - mimetype = response.get('Content-Type', 'text/plain') - if ';' in mimetype: - mimetype = mimetype.split(';')[0] - mimetype = mimetype.strip() - - return ( - response.status_code != 200 or - request.is_ajax() or - getattr(response, 'standalone', False) or - mimetype not in ('text/html', 'text/plain')) - - def unpack(self, request, response): - return getattr(response, '_feincms_unpack', False) - - def render(self, **kwargs): - return getattr(self, 'rendered_result', '') - - def finalize(self, request, response): - headers = getattr(self, 'rendered_headers', None) - if headers: - self._update_response_headers(request, response, headers) - - def _update_response_headers(self, request, response, headers): - """ - Combine all headers that were set by the different content types - We are interested in Cache-Control, Last-Modified, Expires - """ - - # Ideally, for the Cache-Control header, we'd want to do some - # intelligent combining, but that's hard. Let's just collect and unique - # them and let the client worry about that. - cc_headers = set(('must-revalidate',)) - for x in (cc.split(",") for cc in headers.get('Cache-Control', ())): - cc_headers |= set((s.strip() for s in x)) - - if len(cc_headers): - response['Cache-Control'] = ", ".join(cc_headers) - else: # Default value - response['Cache-Control'] = 'no-cache, must-revalidate' - - # Check all Last-Modified headers, choose the latest one - lm_list = [parsedate(x) for x in headers.get('Last-Modified', ())] - if len(lm_list) > 0: - response['Last-Modified'] = http_date(mktime(max(lm_list))) - - # Check all Expires headers, choose the earliest one - lm_list = [parsedate(x) for x in headers.get('Expires', ())] - if len(lm_list) > 0: - response['Expires'] = http_date(mktime(min(lm_list))) - - @classmethod - def app_reverse_cache_key(self, urlconf_path, **kwargs): - return 'FEINCMS:%s:APPCONTENT:%s:%s' % ( - getattr(settings, 'SITE_ID', 0), - get_language(), - urlconf_path, - ) - - @classmethod - def closest_match(cls, urlconf_path): - page_class = cls.parent.field.rel.to - - contents = cls.objects.filter( - parent__in=page_class.objects.active(), - urlconf_path=urlconf_path, - ).order_by('pk').select_related('parent') - - if len(contents) > 1: - try: - current = short_language_code(get_language()) - return [ - content for content in contents if - short_language_code(content.parent.language) == current - ][0] - - except (AttributeError, IndexError): - pass - - try: - return contents[0] - except IndexError: - pass - - return None diff --git a/feincms/apps/reverse.py b/feincms/apps/reverse.py deleted file mode 100644 index 26ee8cade..000000000 --- a/feincms/apps/reverse.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -from functools import wraps - -from django.core.cache import cache -from django.core.urlresolvers import ( - NoReverseMatch, reverse, get_script_prefix, set_script_prefix -) -from django.utils.functional import lazy - - -APP_REVERSE_CACHE_TIMEOUT = 3 - - -def app_reverse(viewname, urlconf=None, args=None, kwargs=None, - *vargs, **vkwargs): - """ - Reverse URLs from application contents - - Works almost like Django's own reverse() method except that it resolves - URLs from application contents. The second argument, ``urlconf``, has to - correspond to the URLconf parameter passed in the ``APPLICATIONS`` list - to ``Page.create_content_type``:: - - app_reverse('mymodel-detail', 'myapp.urls', args=...) - - or - - app_reverse('mymodel-detail', 'myapp.urls', kwargs=...) - - The second argument may also be a request object if you want to reverse - an URL belonging to the current application content. - """ - - # First parameter might be a request instead of an urlconf path, so - # we'll try to be helpful and extract the current urlconf from it - extra_context = getattr(urlconf, '_feincms_extra_context', {}) - appconfig = extra_context.get('app_config', {}) - urlconf = appconfig.get('urlconf_path', urlconf) - - from .contents import ApplicationContent - appcontent_class = ApplicationContent._feincms_content_models[0] - cache_key = appcontent_class.app_reverse_cache_key(urlconf) - url_prefix = cache.get(cache_key) - - if url_prefix is None: - content = appcontent_class.closest_match(urlconf) - - if content is not None: - if urlconf in appcontent_class.ALL_APPS_CONFIG: - # We have an overridden URLconf - app_config = appcontent_class.ALL_APPS_CONFIG[urlconf] - urlconf = app_config['config'].get('urls', urlconf) - - prefix = content.parent.get_absolute_url() - prefix += '/' if prefix[-1] != '/' else '' - - url_prefix = (urlconf, prefix) - cache.set(cache_key, url_prefix, timeout=APP_REVERSE_CACHE_TIMEOUT) - - if url_prefix: - # vargs and vkwargs are used to send through additional parameters - # which are uninteresting to us (such as current_app) - prefix = get_script_prefix() - try: - set_script_prefix(url_prefix[1]) - return reverse( - viewname, - url_prefix[0], - args=args, - kwargs=kwargs, - *vargs, **vkwargs) - finally: - set_script_prefix(prefix) - - raise NoReverseMatch("Unable to find ApplicationContent for %r" % urlconf) - - -#: Lazy version of ``app_reverse`` -app_reverse_lazy = lazy(app_reverse, str) - - -def permalink(func): - """ - Decorator that calls app_reverse() - - Use this instead of standard django.db.models.permalink if you want to - integrate the model through ApplicationContent. The wrapped function - must return 4 instead of 3 arguments:: - - class MyModel(models.Model): - @appmodels.permalink - def get_absolute_url(self): - return ('myapp.urls', 'model_detail', (), {'slug': self.slug}) - """ - def inner(*args, **kwargs): - return app_reverse(*func(*args, **kwargs)) - return wraps(func)(inner) diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index 854ab307b..4f3a215a1 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -1,13 +1,75 @@ -# flake8: noqa -from __future__ import absolute_import, unicode_literals +from __future__ import absolute_import +from email.utils import parsedate +from functools import partial, wraps +from time import mktime import warnings -from feincms.apps import * +from django.conf import settings +from django.core.cache import cache +from django.core.urlresolvers import ( + NoReverseMatch, reverse, get_script_prefix, set_script_prefix, + Resolver404, resolve, +) +from django.db import models +from django.http import HttpResponse +from django.template.response import TemplateResponse +from django.utils.functional import lazy +from django.utils.http import http_date +from django.utils.safestring import mark_safe +from django.utils.translation import get_language, ugettext_lazy as _ -warnings.warn( - 'Import ApplicationContent and friends from feincms.apps.', - DeprecationWarning, stacklevel=2) +from feincms.admin.item_editor import ItemEditorForm +from feincms.contrib.fields import JSONField +from feincms.translations import short_language_code +from feincms.utils import get_object + + +APP_REVERSE_CACHE_TIMEOUT = 3 + + +__all__ = ( + 'ApplicationContent', + 'app_reverse', 'app_reverse_lazy', 'permalink', + 'UnpackTemplateResponse', 'standalone', 'unpack', +) + + +class UnpackTemplateResponse(TemplateResponse): + """ + Completely the same as marking applicationcontent-contained views with + the ``feincms.views.decorators.unpack`` decorator. + """ + _feincms_unpack = True + + +def standalone(view_func): + """ + Marks the view method as standalone view; this means that + ``HttpResponse`` objects returned from ``ApplicationContent`` + are returned directly, without further processing. + """ + + def inner(request, *args, **kwargs): + response = view_func(request, *args, **kwargs) + if isinstance(response, HttpResponse): + response.standalone = True + return response + return wraps(view_func)(inner) + + +def unpack(view_func): + """ + Marks the returned response as to-be-unpacked if it is a + ``TemplateResponse``. + """ + + def inner(request, *args, **kwargs): + response = view_func(request, *args, **kwargs) + if isinstance(response, TemplateResponse): + response._feincms_unpack = True + return response + return wraps(view_func)(inner) def cycle_app_reverse_cache(*args, **kwargs): @@ -16,3 +78,370 @@ def cycle_app_reverse_cache(*args, **kwargs): ' a future version of FeinCMS.', DeprecationWarning, stacklevel=2, ) + + +def app_reverse(viewname, urlconf=None, args=None, kwargs=None, + *vargs, **vkwargs): + """ + Reverse URLs from application contents + + Works almost like Django's own reverse() method except that it resolves + URLs from application contents. The second argument, ``urlconf``, has to + correspond to the URLconf parameter passed in the ``APPLICATIONS`` list + to ``Page.create_content_type``:: + + app_reverse('mymodel-detail', 'myapp.urls', args=...) + + or + + app_reverse('mymodel-detail', 'myapp.urls', kwargs=...) + + The second argument may also be a request object if you want to reverse + an URL belonging to the current application content. + """ + + # First parameter might be a request instead of an urlconf path, so + # we'll try to be helpful and extract the current urlconf from it + extra_context = getattr(urlconf, '_feincms_extra_context', {}) + appconfig = extra_context.get('app_config', {}) + urlconf = appconfig.get('urlconf_path', urlconf) + + appcontent_class = ApplicationContent._feincms_content_models[0] + cache_key = appcontent_class.app_reverse_cache_key(urlconf) + url_prefix = cache.get(cache_key) + + if url_prefix is None: + content = appcontent_class.closest_match(urlconf) + + if content is not None: + if urlconf in appcontent_class.ALL_APPS_CONFIG: + # We have an overridden URLconf + app_config = appcontent_class.ALL_APPS_CONFIG[urlconf] + urlconf = app_config['config'].get('urls', urlconf) + + prefix = content.parent.get_absolute_url() + prefix += '/' if prefix[-1] != '/' else '' + + url_prefix = (urlconf, prefix) + cache.set(cache_key, url_prefix, timeout=APP_REVERSE_CACHE_TIMEOUT) + + if url_prefix: + # vargs and vkwargs are used to send through additional parameters + # which are uninteresting to us (such as current_app) + prefix = get_script_prefix() + try: + set_script_prefix(url_prefix[1]) + return reverse( + viewname, + url_prefix[0], + args=args, + kwargs=kwargs, + *vargs, **vkwargs) + finally: + set_script_prefix(prefix) + + raise NoReverseMatch("Unable to find ApplicationContent for %r" % urlconf) + + +#: Lazy version of ``app_reverse`` +app_reverse_lazy = lazy(app_reverse, str) + + +def permalink(func): + """ + Decorator that calls app_reverse() + + Use this instead of standard django.db.models.permalink if you want to + integrate the model through ApplicationContent. The wrapped function + must return 4 instead of 3 arguments:: + + class MyModel(models.Model): + @appmodels.permalink + def get_absolute_url(self): + return ('myapp.urls', 'model_detail', (), {'slug': self.slug}) + """ + def inner(*args, **kwargs): + return app_reverse(*func(*args, **kwargs)) + return wraps(func)(inner) + + +class ApplicationContent(models.Model): + #: parameters is used to serialize instance-specific data which will be + # provided to the view code. This allows customization (e.g. "Embed + # MyBlogApp for blog ") + parameters = JSONField(null=True, editable=False) + + ALL_APPS_CONFIG = {} + + class Meta: + abstract = True + verbose_name = _('application content') + verbose_name_plural = _('application contents') + + @classmethod + def initialize_type(cls, APPLICATIONS): + for i in APPLICATIONS: + if not 2 <= len(i) <= 3: + raise ValueError( + "APPLICATIONS must be provided with tuples containing at" + " least two parameters (urls, name) and an optional extra" + " config dict") + + urls, name = i[0:2] + + if len(i) == 3: + app_conf = i[2] + + if not isinstance(app_conf, dict): + raise ValueError( + "The third parameter of an APPLICATIONS entry must be" + " a dict or the name of one!") + else: + app_conf = {} + + cls.ALL_APPS_CONFIG[urls] = { + "urls": urls, + "name": name, + "config": app_conf + } + + cls.add_to_class( + 'urlconf_path', + models.CharField(_('application'), max_length=100, choices=[ + (c['urls'], c['name']) for c in cls.ALL_APPS_CONFIG.values()]) + ) + + class ApplicationContentItemEditorForm(ItemEditorForm): + app_config = {} + custom_fields = {} + + def __init__(self, *args, **kwargs): + super(ApplicationContentItemEditorForm, self).__init__( + *args, **kwargs) + + instance = kwargs.get("instance", None) + + if instance: + try: + # TODO use urlconf_path from POST if set + # urlconf_path = request.POST.get('...urlconf_path', + # instance.urlconf_path) + self.app_config = cls.ALL_APPS_CONFIG[ + instance.urlconf_path]['config'] + except KeyError: + self.app_config = {} + + self.custom_fields = {} + admin_fields = self.app_config.get('admin_fields', {}) + + if isinstance(admin_fields, dict): + self.custom_fields.update(admin_fields) + else: + get_fields = get_object(admin_fields) + self.custom_fields.update( + get_fields(self, *args, **kwargs)) + + params = self.instance.parameters + for k, v in self.custom_fields.items(): + v.initial = params.get(k) + self.fields[k] = v + if k in params: + self.fields[k].initial = params[k] + + def save(self, commit=True, *args, **kwargs): + # Django ModelForms return the model instance from save. We'll + # call save with commit=False first to do any necessary work & + # get the model so we can set .parameters to the values of our + # custom fields before calling save(commit=True) + + m = super(ApplicationContentItemEditorForm, self).save( + commit=False, *args, **kwargs) + + m.parameters = dict( + (k, self.cleaned_data[k]) + for k in self.custom_fields if k in self.cleaned_data) + + if commit: + m.save(**kwargs) + + return m + + # This provides hooks for us to customize the admin interface for + # embedded instances: + cls.feincms_item_editor_form = ApplicationContentItemEditorForm + + def __init__(self, *args, **kwargs): + super(ApplicationContent, self).__init__(*args, **kwargs) + self.app_config = self.ALL_APPS_CONFIG.get( + self.urlconf_path, {}).get('config', {}) + + def process(self, request, **kw): + page_url = self.parent.get_absolute_url() + + # Provide a way for appcontent items to customize URL processing by + # altering the perceived path of the page: + if "path_mapper" in self.app_config: + path_mapper = get_object(self.app_config["path_mapper"]) + path, page_url = path_mapper( + request.path, + page_url, + appcontent_parameters=self.parameters + ) + else: + path = request._feincms_extra_context['extra_path'] + + # Resolve the module holding the application urls. + urlconf_path = self.app_config.get('urls', self.urlconf_path) + + try: + fn, args, kwargs = resolve(path, urlconf_path) + except (ValueError, Resolver404): + raise Resolver404(str('Not found (resolving %r in %r failed)') % ( + path, urlconf_path)) + + # Variables from the ApplicationContent parameters are added to request + # so we can expose them to our templates via the appcontent_parameters + # context_processor + request._feincms_extra_context.update(self.parameters) + + # Save the application configuration for reuse elsewhere + request._feincms_extra_context.update({ + 'app_config': dict( + self.app_config, + urlconf_path=self.urlconf_path, + ), + }) + + view_wrapper = self.app_config.get("view_wrapper", None) + if view_wrapper: + fn = partial( + get_object(view_wrapper), + view=fn, + appcontent_parameters=self.parameters + ) + + output = fn(request, *args, **kwargs) + + if isinstance(output, HttpResponse): + if self.send_directly(request, output): + return output + elif output.status_code == 200: + + if self.unpack(request, output) and 'view' in kw: + # Handling of @unpack and UnpackTemplateResponse + kw['view'].template_name = output.template_name + kw['view'].request._feincms_extra_context.update( + output.context_data) + + else: + # If the response supports deferred rendering, render the + # response right now. We do not handle template response + # middleware. + if hasattr(output, 'render') and callable(output.render): + output.render() + + self.rendered_result = mark_safe( + output.content.decode('utf-8')) + + self.rendered_headers = {} + + # Copy relevant headers for later perusal + for h in ('Cache-Control', 'Last-Modified', 'Expires'): + if h in output: + self.rendered_headers.setdefault( + h, []).append(output[h]) + + elif isinstance(output, tuple) and 'view' in kw: + kw['view'].template_name = output[0] + kw['view'].request._feincms_extra_context.update(output[1]) + + else: + self.rendered_result = mark_safe(output) + + return True # successful + + def send_directly(self, request, response): + mimetype = response.get('Content-Type', 'text/plain') + if ';' in mimetype: + mimetype = mimetype.split(';')[0] + mimetype = mimetype.strip() + + return ( + response.status_code != 200 or + request.is_ajax() or + getattr(response, 'standalone', False) or + mimetype not in ('text/html', 'text/plain')) + + def unpack(self, request, response): + return getattr(response, '_feincms_unpack', False) + + def render(self, **kwargs): + return getattr(self, 'rendered_result', '') + + def finalize(self, request, response): + headers = getattr(self, 'rendered_headers', None) + if headers: + self._update_response_headers(request, response, headers) + + def _update_response_headers(self, request, response, headers): + """ + Combine all headers that were set by the different content types + We are interested in Cache-Control, Last-Modified, Expires + """ + + # Ideally, for the Cache-Control header, we'd want to do some + # intelligent combining, but that's hard. Let's just collect and unique + # them and let the client worry about that. + cc_headers = set(('must-revalidate',)) + for x in (cc.split(",") for cc in headers.get('Cache-Control', ())): + cc_headers |= set((s.strip() for s in x)) + + if len(cc_headers): + response['Cache-Control'] = ", ".join(cc_headers) + else: # Default value + response['Cache-Control'] = 'no-cache, must-revalidate' + + # Check all Last-Modified headers, choose the latest one + lm_list = [parsedate(x) for x in headers.get('Last-Modified', ())] + if len(lm_list) > 0: + response['Last-Modified'] = http_date(mktime(max(lm_list))) + + # Check all Expires headers, choose the earliest one + lm_list = [parsedate(x) for x in headers.get('Expires', ())] + if len(lm_list) > 0: + response['Expires'] = http_date(mktime(min(lm_list))) + + @classmethod + def app_reverse_cache_key(self, urlconf_path, **kwargs): + return 'FEINCMS:%s:APPCONTENT:%s:%s' % ( + getattr(settings, 'SITE_ID', 0), + get_language(), + urlconf_path, + ) + + @classmethod + def closest_match(cls, urlconf_path): + page_class = cls.parent.field.rel.to + + contents = cls.objects.filter( + parent__in=page_class.objects.active(), + urlconf_path=urlconf_path, + ).order_by('pk').select_related('parent') + + if len(contents) > 1: + try: + current = short_language_code(get_language()) + return [ + content for content in contents if + short_language_code(content.parent.language) == current + ][0] + + except (AttributeError, IndexError): + pass + + try: + return contents[0] + except IndexError: + pass + + return None From f2a78e4c0cd97f3a1547b1b243ee0436981ed6db Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Tue, 9 Feb 2016 12:07:57 +0100 Subject: [PATCH 362/654] Adjust dev status on .pre versions too --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 5ea12b61c..fc93aaf78 100755 --- a/setup.py +++ b/setup.py @@ -14,6 +14,8 @@ def read(filename): devstatus = 'Development Status :: 5 - Production/Stable' if '.dev' in version: devstatus = 'Development Status :: 3 - Alpha' +else if '.pre' in version: + devstatus = 'Development Status :: 4 - Beta' setup( name='FeinCMS', From 4e23fc9bd6601d58af1cd175f6f3e67daa64eb26 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Tue, 9 Feb 2016 12:31:26 +0100 Subject: [PATCH 363/654] Back out old change that came back in 9d8ee30cf712b7757784c8d518f8f2d76e5f6c5a to haunt us --- feincms/__init__.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 1dbac4f5b..0426f87a7 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 11, 5, 'dev') +VERSION = (1, 12, 0, 'pre') __version__ = '.'.join(map(str, VERSION)) @@ -44,14 +44,9 @@ def ensure_completely_loaded(force=False): if COMPLETELY_LOADED and not force: return True - try: - from django.apps import apps - except ImportError: - from django.db.models import loading as apps - else: - # Django 1.7 and up - if not apps.ready: - return + from django.apps import apps + if not apps.ready: + return # Ensure meta information concerning related fields is up-to-date. # Upon accessing the related fields information from Model._meta, @@ -65,8 +60,7 @@ def ensure_completely_loaded(force=False): import django if django.get_version() < '1.8': - from feincms._internal import get_models - for model in get_models(): + for model in apps.get_models(): for cache_name in ( '_field_cache', '_field_name_cache', '_m2m_cache', '_related_objects_cache', '_related_many_to_many_cache', @@ -89,15 +83,9 @@ def ensure_completely_loaded(force=False): # on a model validation error (Django 1.4 doesn't exhibit this # problem). See Issue #323 on github. if hasattr(apps, 'cache'): - try: - apps.cache.get_models.cache_clear() # Django 1.7+ - except AttributeError: - apps.cache._get_models_cache.clear() # Django 1.6- - - if hasattr(apps, 'ready'): - if apps.ready: - COMPLETELY_LOADED = True - elif apps.app_cache_ready(): - COMPLETELY_LOADED = True + apps.cache.get_models.cache_clear() + + if apps.ready: + COMPLETELY_LOADED = True return True From 3bf8f18e60dd163c79b884a61815f12e12eba5ed Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 11 Feb 2016 16:25:22 +0100 Subject: [PATCH 364/654] Hmm --- docs/releases/1.12.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst index 9650e473c..b798f039c 100644 --- a/docs/releases/1.12.rst +++ b/docs/releases/1.12.rst @@ -45,6 +45,11 @@ Also, you need to add a model migration which renames the old migrations.RenameField('TemplateContent', 'filename', 'template'), ] +Also, prepend ``content/template/`` to all values:: + + UPDATE page_page_templatecontent + SET template='content/template/' || template; + The blog module has been completely removed ============================================ From de6b943fff2ee675cc5d58110fdf776c00605f7f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 11 Feb 2016 17:11:20 +0100 Subject: [PATCH 365/654] Better --- docs/releases/1.12.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst index b798f039c..3622d2f97 100644 --- a/docs/releases/1.12.rst +++ b/docs/releases/1.12.rst @@ -27,7 +27,8 @@ be explicitly specified when creating the content type:: ]) Also, you need to add a model migration which renames the old -``filename`` field to the new ``template`` field:: +``filename`` field to the new ``template`` field and prepends +``content/template/`` to all filenames:: # -*- coding: utf-8 -*- from __future__ import unicode_literals @@ -43,13 +44,14 @@ Also, you need to add a model migration which renames the old operations = [ migrations.RenameField('TemplateContent', 'filename', 'template'), + migrations.RunSQL( + "UPDATE page_page_templatecontent" + " SET template='content/template/' || template;", + "UPDATE page_page_templatecontent" + " SET template=REPLACE(template, 'content/template/', '');" + ), ] -Also, prepend ``content/template/`` to all values:: - - UPDATE page_page_templatecontent - SET template='content/template/' || template; - The blog module has been completely removed ============================================ From 824f2f35c9d686086f4180082241b960a3df2b24 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 11 Feb 2016 17:22:10 +0100 Subject: [PATCH 366/654] Huh? --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fc93aaf78..c8cdb5c0b 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def read(filename): devstatus = 'Development Status :: 5 - Production/Stable' if '.dev' in version: devstatus = 'Development Status :: 3 - Alpha' -else if '.pre' in version: +elif '.pre' in version: devstatus = 'Development Status :: 4 - Beta' setup( From 72a11fe2be2c3932171d84d65083cc07aac02069 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 14 Mar 2016 17:35:04 +0100 Subject: [PATCH 367/654] get_redirect_to_target might not get a request Refs 32bbfd3add6d6ffe378fc50627769e4341f38b8e --- feincms/extensions/translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index ae2a95e53..628436bc0 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -183,7 +183,7 @@ def handle_model(self): original_get_redirect_to_target = cls.get_redirect_to_target @monkeypatch_method(cls) - def get_redirect_to_target(self, request): + def get_redirect_to_target(self, request=None): """ Find an acceptable redirect target. If this is a local link, then try to find the page this redirect references and From b0b06fc789dc4cf6acd4672e5820bcb0920ab243 Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 10:40:37 +0200 Subject: [PATCH 368/654] Patterns deprecation warning fix (Django 1.10). --- feincms/urls.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/feincms/urls.py b/feincms/urls.py index c7bccb831..94c7de494 100644 --- a/feincms/urls.py +++ b/feincms/urls.py @@ -7,8 +7,7 @@ handler = Handler.as_view() -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', handler, name='feincms_home'), - url(r'^(.*)/$', handler, name='feincms_handler'), -) + url(r'^(.*)/$', handler, name='feincms_handler') +] From 19c3417cea970c358f6445729baa61c58afd7e70 Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 10:42:01 +0200 Subject: [PATCH 369/654] Language detection via session - fix for translation extension. --- feincms/module/extensions/translations.py | 7 ++++--- tests/testapp/tests/test_extensions.py | 24 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index 1ec9ee995..a8c6dc065 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -35,6 +35,7 @@ logger = logging.getLogger(__name__) LANGUAGE_COOKIE_NAME = django_settings.LANGUAGE_COOKIE_NAME +LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY # ------------------------------------------------------------------------ @@ -45,7 +46,7 @@ def user_has_language_set(request): site's language settings, after all, the user's decision is what counts. """ if (hasattr(request, 'session') - and request.session.get(LANGUAGE_COOKIE_NAME) is not None): + and request.session.get(LANGUAGE_SESSION_KEY) is not None): return True if LANGUAGE_COOKIE_NAME in request.COOKIES: return True @@ -87,8 +88,8 @@ def translation_set_language(request, select_language): if hasattr(request, 'session'): # User has a session, then set this language there - if select_language != request.session.get(LANGUAGE_COOKIE_NAME): - request.session[LANGUAGE_COOKIE_NAME] = select_language + if select_language != request.session.get(LANGUAGE_SESSION_KEY): + request.session[LANGUAGE_SESSION_KEY] = select_language elif request.method == 'GET' and not fallback: # No session is active. We need to set a cookie for the language # so that it persists when users change their location to somewhere diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 60ad3c05f..140d889ff 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -4,9 +4,11 @@ from django.contrib.sites.models import Site from django.template.defaultfilters import slugify -from django.test import TestCase +from django.test import TestCase, RequestFactory +from django.utils.translation import LANGUAGE_SESSION_KEY from feincms.module.page.models import Page +from feincms.module.extensions.translations import user_has_language_set, translation_set_language class TranslationTestCase(TestCase): @@ -61,3 +63,23 @@ def testPage(self): # TODO: add request tests # with translation.override('de'): + + def test_user_has_language_set(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + setattr(request, 'session', dict()) + request.session[LANGUAGE_SESSION_KEY] = 'en' + self.assertEqual(user_has_language_set(request), True) + + setattr(request, 'session', dict()) + request.COOKIES['django_language'] = 'en' + self.assertEqual(user_has_language_set(request), True) + + def test_translation_set_language(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + setattr(request, 'session', dict()) + translation_set_language(request, 'en') + + self.assertEqual(request.LANGUAGE_CODE, 'en') + self.assertEqual(request.session[LANGUAGE_SESSION_KEY], 'en') From 0e41e1f2c3a9a8a0f6179608fc9b67c69319398b Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 10:57:25 +0200 Subject: [PATCH 370/654] More url patterns deprecation warning fixes. --- feincms/contrib/preview/urls.py | 9 ++++----- feincms/module/medialibrary/modeladmins.py | 9 ++++----- feincms/urls.py | 2 +- tests/testapp/applicationcontent_urls.py | 9 ++++----- tests/testapp/blog_urls.py | 9 ++++----- tests/testapp/urls.py | 9 ++++----- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/feincms/contrib/preview/urls.py b/feincms/contrib/preview/urls.py index 83bc5e4d8..128c6753b 100644 --- a/feincms/contrib/preview/urls.py +++ b/feincms/contrib/preview/urls.py @@ -1,10 +1,9 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url from feincms.contrib.preview.views import PreviewHandler -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^(.*)/_preview/(\d+)/$', PreviewHandler.as_view(), - name='feincms_preview'), -) + name='feincms_preview') +] diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 6c701a3f2..a745e136b 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -124,18 +124,17 @@ class MediaFileAdmin(ExtensionModelAdmin): actions = [assign_category, save_as_zipfile] def get_urls(self): - from django.conf.urls import patterns, url + from django.conf.urls import url urls = super(MediaFileAdmin, self).get_urls() - my_urls = patterns( - '', + my_urls = [ url( r'^mediafile-bulk-upload/$', self.admin_site.admin_view(MediaFileAdmin.bulk_upload), {}, name='mediafile_bulk_upload', - ), - ) + ) + ] return my_urls + urls diff --git a/feincms/urls.py b/feincms/urls.py index 94c7de494..b32df201e 100644 --- a/feincms/urls.py +++ b/feincms/urls.py @@ -1,7 +1,7 @@ # flake8: noqa from __future__ import absolute_import -from django.conf.urls import patterns, url +from django.conf.urls import url from feincms.views.cbv.views import Handler diff --git a/tests/testapp/applicationcontent_urls.py b/tests/testapp/applicationcontent_urls.py index 7b83b46bd..16f990da7 100644 --- a/tests/testapp/applicationcontent_urls.py +++ b/tests/testapp/applicationcontent_urls.py @@ -4,7 +4,7 @@ from __future__ import absolute_import, unicode_literals -from django.conf.urls import patterns, url +from django.conf.urls import url from django.http import HttpResponse, HttpResponseRedirect from django.template.loader import render_to_string from django.template.response import TemplateResponse @@ -54,8 +54,7 @@ def inheritance20_unpack(request): return response -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^$', module_root, name='ac_module_root'), url(r'^args_test/([^/]+)/([^/]+)/$', args_test, name='ac_args_test'), url(r'^kwargs_test/(?P[^/]+)/(?P[^/]+)/$', args_test), @@ -66,5 +65,5 @@ def inheritance20_unpack(request): url(r'^response/$', response), url(r'^response_decorated/$', standalone(response)), url(r'^inheritance20/$', inheritance20), - url(r'^inheritance20_unpack/$', inheritance20_unpack), -) + url(r'^inheritance20_unpack/$', inheritance20_unpack) +] diff --git a/tests/testapp/blog_urls.py b/tests/testapp/blog_urls.py index 90d7ca2ce..c374da478 100644 --- a/tests/testapp/blog_urls.py +++ b/tests/testapp/blog_urls.py @@ -1,11 +1,10 @@ -from django.conf.urls import patterns, url +from django.conf.urls import url from django.views import generic from feincms.module.blog.models import Entry -urlpatterns = patterns( - '', +urlpatterns = [ url( r'^(?P\d+)/', generic.DetailView.as_view( @@ -19,5 +18,5 @@ queryset=Entry.objects.all(), ), name='blog_entry_list' - ), -) + ) +] diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py index 7610a56ea..2ff78cc04 100644 --- a/tests/testapp/urls.py +++ b/tests/testapp/urls.py @@ -2,7 +2,7 @@ import os -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns @@ -13,8 +13,7 @@ admin.autodiscover() -urlpatterns = patterns( - '', +urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url( @@ -30,7 +29,7 @@ ), url(r'', include('feincms.contrib.preview.urls')), - url(r'', include('feincms.views.cbv.urls')), -) + url(r'', include('feincms.views.cbv.urls')) +] urlpatterns += staticfiles_urlpatterns() From 141e40e795e7d9d132d26f77a01925f0af24fe94 Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 12:20:27 +0200 Subject: [PATCH 371/654] Translation extension LANGUAGE_SESSION_KEY related tests cleanup. --- tests/testapp/tests/test_extensions.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 140d889ff..405a96ca9 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -6,6 +6,7 @@ from django.template.defaultfilters import slugify from django.test import TestCase, RequestFactory from django.utils.translation import LANGUAGE_SESSION_KEY +from django.conf import settings as django_settings from feincms.module.page.models import Page from feincms.module.extensions.translations import user_has_language_set, translation_set_language @@ -64,18 +65,21 @@ def testPage(self): # TODO: add request tests # with translation.override('de'): - def test_user_has_language_set(self): + def test_user_has_language_set_with_session(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) setattr(request, 'session', dict()) request.session[LANGUAGE_SESSION_KEY] = 'en' self.assertEqual(user_has_language_set(request), True) - setattr(request, 'session', dict()) - request.COOKIES['django_language'] = 'en' + def test_user_has_language_set_with_cookie(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + request.COOKIES[django_settings.LANGUAGE_COOKIE_NAME] = 'en' + self.assertEqual(user_has_language_set(request), True) - def test_translation_set_language(self): + def test_translation_set_language_to_session(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) setattr(request, 'session', dict()) @@ -83,3 +87,11 @@ def test_translation_set_language(self): self.assertEqual(request.LANGUAGE_CODE, 'en') self.assertEqual(request.session[LANGUAGE_SESSION_KEY], 'en') + + def test_translation_set_language_to_cookie(self): + factory = RequestFactory() + request = factory.get(self.page_en.get_navigation_url()) + response = translation_set_language(request, 'en') + + self.assertEqual(request.LANGUAGE_CODE, 'en') + self.assertEqual(response.cookies[django_settings.LANGUAGE_COOKIE_NAME].value, 'en') \ No newline at end of file From 7addc2853844841633cb56ca14b5901e1c9d137c Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 13:00:58 +0200 Subject: [PATCH 372/654] Coding style fix. Added LANGUAGE_SESSION_KEY fallback for Django 1.6. --- feincms/module/extensions/translations.py | 6 +++++- tests/testapp/tests/test_extensions.py | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index a8c6dc065..092e105ad 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -35,7 +35,11 @@ logger = logging.getLogger(__name__) LANGUAGE_COOKIE_NAME = django_settings.LANGUAGE_COOKIE_NAME -LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY +if translation.LANGUAGE_SESSION_KEY: + LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY +else: + # Django 1.6 + LANGUAGE_SESSION_KEY = LANGUAGE_COOKIE_NAME # ------------------------------------------------------------------------ diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 405a96ca9..0ecfb0a39 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -5,11 +5,12 @@ from django.contrib.sites.models import Site from django.template.defaultfilters import slugify from django.test import TestCase, RequestFactory -from django.utils.translation import LANGUAGE_SESSION_KEY +from django.utils import translation from django.conf import settings as django_settings from feincms.module.page.models import Page -from feincms.module.extensions.translations import user_has_language_set, translation_set_language +from feincms.module.extensions.translations import user_has_language_set,\ + translation_set_language class TranslationTestCase(TestCase): @@ -35,6 +36,12 @@ def setUp(self): self.page_de = de.parent self.page_en = en.parent + if translation.LANGUAGE_SESSION_KEY: + self.language_session_key = translation.LANGUAGE_SESSION_KEY + else: + # Django 1.6 + self.language_session_key = django_settings.LANGUAGE_COOKIE_NAME + def create_page(self, title='Test page', parent=None, **kwargs): defaults = { 'template_key': 'base', @@ -69,7 +76,7 @@ def test_user_has_language_set_with_session(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) setattr(request, 'session', dict()) - request.session[LANGUAGE_SESSION_KEY] = 'en' + request.session[self.language_session_key] = 'en' self.assertEqual(user_has_language_set(request), True) def test_user_has_language_set_with_cookie(self): @@ -86,7 +93,7 @@ def test_translation_set_language_to_session(self): translation_set_language(request, 'en') self.assertEqual(request.LANGUAGE_CODE, 'en') - self.assertEqual(request.session[LANGUAGE_SESSION_KEY], 'en') + self.assertEqual(request.session[self.language_session_key], 'en') def test_translation_set_language_to_cookie(self): factory = RequestFactory() @@ -94,4 +101,4 @@ def test_translation_set_language_to_cookie(self): response = translation_set_language(request, 'en') self.assertEqual(request.LANGUAGE_CODE, 'en') - self.assertEqual(response.cookies[django_settings.LANGUAGE_COOKIE_NAME].value, 'en') \ No newline at end of file + self.assertEqual(response.cookies[django_settings.LANGUAGE_COOKIE_NAME].value, 'en') From 364e283e163332f7612454ceac486e70b610efe1 Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 13:08:59 +0200 Subject: [PATCH 373/654] Flake8 warning fix. --- tests/testapp/tests/test_extensions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 0ecfb0a39..68d1179d0 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -9,8 +9,8 @@ from django.conf import settings as django_settings from feincms.module.page.models import Page -from feincms.module.extensions.translations import user_has_language_set,\ - translation_set_language +from feincms.module.extensions.translations import user_has_language_set +from feincms.module.extensions.translations import translation_set_language class TranslationTestCase(TestCase): @@ -101,4 +101,6 @@ def test_translation_set_language_to_cookie(self): response = translation_set_language(request, 'en') self.assertEqual(request.LANGUAGE_CODE, 'en') - self.assertEqual(response.cookies[django_settings.LANGUAGE_COOKIE_NAME].value, 'en') + + c_key = django_settings.LANGUAGE_COOKIE_NAME + self.assertEqual(response.cookies[c_key].value, 'en') From 7995da8f2d87f5034c7e3da30b613b6c602f9c63 Mon Sep 17 00:00:00 2001 From: saboter Date: Fri, 8 Apr 2016 13:15:21 +0200 Subject: [PATCH 374/654] Django 1.6 fallback for LANGUAGE_SESSION_KEY (this time, working implementation). --- feincms/module/extensions/translations.py | 2 +- tests/testapp/tests/test_extensions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index 092e105ad..d74f3602c 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -35,7 +35,7 @@ logger = logging.getLogger(__name__) LANGUAGE_COOKIE_NAME = django_settings.LANGUAGE_COOKIE_NAME -if translation.LANGUAGE_SESSION_KEY: +if hasattr(translation, 'LANGUAGE_SESSION_KEY'): LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY else: # Django 1.6 diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 68d1179d0..7815d1c27 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -36,7 +36,7 @@ def setUp(self): self.page_de = de.parent self.page_en = en.parent - if translation.LANGUAGE_SESSION_KEY: + if hasattr(translation, 'LANGUAGE_SESSION_KEY'): self.language_session_key = translation.LANGUAGE_SESSION_KEY else: # Django 1.6 From 2f170667800d28f340f720343684d78f00331b8c Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Tue, 12 Apr 2016 12:30:01 +0200 Subject: [PATCH 375/654] Factor out handling of "page.page:42" type strings to own functions match_model_string() and get_model_instance() in feincms.utils. This allows for other content types to also use that linking notation without reimplementing the parsing logic. --- feincms/module/page/models.py | 28 ++++------------------- feincms/utils/__init__.py | 43 ++++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index d1968570c..ba3151662 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -21,13 +21,7 @@ from feincms.module.mixins import ContentModelMixin from feincms.module.page import processors from feincms.utils.managers import ActiveAwareContentManagerMixin - -from feincms.utils import shorten_string - - -REDIRECT_TO_RE = re.compile( - r'^(?P\w+).(?P\w+):(?P\d+)$') - +from feincms.utils import shorten_string, match_model_string, get_model_instance # ------------------------------------------------------------------------ class BasePageManager(ActiveAwareContentManagerMixin, TreeManager): @@ -336,25 +330,11 @@ def get_redirect_to_page(self): return None # It might be an identifier for a different object - match = REDIRECT_TO_RE.match(self.redirect_to) - - # It's not, oh well. - if not match: + whereto = match_model_string(self.redirect_to) + if not whereto: return None - matches = match.groupdict() - model = apps.get_model(matches['app_label'], matches['model_name']) - - if not model: - return None - - try: - instance = model._default_manager.get(pk=int(matches['pk'])) - return instance - except models.ObjectDoesNotExist: - pass - - return None + return get_model_instance(*whereto) def get_redirect_to_target(self, request=None): """ diff --git a/feincms/utils/__init__.py b/feincms/utils/__init__.py index 48a346908..28c6b69d3 100644 --- a/feincms/utils/__init__.py +++ b/feincms/utils/__init__.py @@ -4,6 +4,8 @@ from __future__ import absolute_import, division, unicode_literals +import re + from importlib import import_module from django.apps import apps @@ -13,7 +15,6 @@ from feincms import settings - # ------------------------------------------------------------------------ def get_object(path, fail_silently=False): # Return early if path isn't a string (might already be an callable or @@ -33,6 +34,46 @@ def get_object(path, fail_silently=False): if not fail_silently: raise +# ------------------------------------------------------------------------ +def get_model_instance(app_label, model_name, pk): + """ + Find an object instance given an app_label, a model name and the + object's pk. + + This is used for page's get_link_target but can be used for other + content types that accept e.g. either an internal or external link. + """ + + model = apps.get_model(app_label, model_name) + if not model: + return None + + try: + instance = model._default_manager.get(pk=pk) + return instance + except model.ObjectDoesNotExist: + pass + + return None + +# ------------------------------------------------------------------------ +REDIRECT_TO_RE = re.compile( + r'^(?P\w+).(?P\w+):(?P\d+)$') + +def match_model_string(s): + """ + Try to parse a string in format "app_label.model_name:pk", as is used + Page.get_link_target() + + Returns a tuple app_label, model_name, pk or None if the string + does not match the expected format. + """ + + match = REDIRECT_TO_RE.match(s) + if not match: + return None + matches = match.groupdict() + return (matches['app_label'], matches['model_name'], int(matches['pk'])) # ------------------------------------------------------------------------ def copy_model_instance(obj, exclude=None): From a06e6cc3c0b0440d3adedd1ccce78309d8fae9a9 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Wed, 13 Apr 2016 13:13:45 +0200 Subject: [PATCH 376/654] Allow navigationgroup to be blank --- feincms/module/page/extensions/navigationgroups.py | 1 + 1 file changed, 1 insertion(+) diff --git a/feincms/module/page/extensions/navigationgroups.py b/feincms/module/page/extensions/navigationgroups.py index fb07eb582..fc47ec033 100644 --- a/feincms/module/page/extensions/navigationgroups.py +++ b/feincms/module/page/extensions/navigationgroups.py @@ -26,6 +26,7 @@ def handle_model(self): choices=self.groups, default=self.groups[0][0], max_length=20, + blank=True, db_index=True)) def handle_modeladmin(self, modeladmin): From 498ae812e338da67a0b2acfbd179779cfb39a50b Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Tue, 19 Apr 2016 10:37:11 +0200 Subject: [PATCH 377/654] Add on_delete=PROTECT to media file reference, so deleting a media file won't silently delete all ContentWithMediaFile. --- feincms/module/medialibrary/fields.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/feincms/module/medialibrary/fields.py b/feincms/module/medialibrary/fields.py index 09279b4f6..71d8073d9 100644 --- a/feincms/module/medialibrary/fields.py +++ b/feincms/module/medialibrary/fields.py @@ -69,7 +69,9 @@ class feincms_item_editor_inline(FeinCMSInline): raw_id_fields = ('mediafile',) mediafile = MediaFileForeignKey( - MediaFile, verbose_name=_('media file'), related_name='+') + MediaFile, verbose_name=_('media file'), related_name='+', + on_delete=models.PROTECT + ) class Meta: abstract = True From fa854d70bac6a5ffa6519e32c96a7fece26f869b Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 27 Apr 2016 14:07:02 +0200 Subject: [PATCH 378/654] Add back changes lost in dirty merge --- feincms/extensions/translations.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index 628436bc0..d02d31337 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -17,7 +17,6 @@ from __future__ import absolute_import, unicode_literals - # ------------------------------------------------------------------------ import logging @@ -34,7 +33,13 @@ # ------------------------------------------------------------------------ logger = logging.getLogger(__name__) + LANGUAGE_COOKIE_NAME = django_settings.LANGUAGE_COOKIE_NAME +if hasattr(translation, 'LANGUAGE_SESSION_KEY'): + LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY +else: + # Django 1.6 + LANGUAGE_SESSION_KEY = LANGUAGE_COOKIE_NAME # ------------------------------------------------------------------------ @@ -44,8 +49,8 @@ def user_has_language_set(request): This is taken later on as an indication that we should not mess with the site's language settings, after all, the user's decision is what counts. """ - if (hasattr(request, 'session') and - request.session.get(LANGUAGE_COOKIE_NAME) is not None): + if (hasattr(request, 'session') + and request.session.get(LANGUAGE_SESSION_KEY) is not None): return True if LANGUAGE_COOKIE_NAME in request.COOKIES: return True @@ -87,8 +92,8 @@ def translation_set_language(request, select_language): if hasattr(request, 'session'): # User has a session, then set this language there - if select_language != request.session.get(LANGUAGE_COOKIE_NAME): - request.session[LANGUAGE_COOKIE_NAME] = select_language + if select_language != request.session.get(LANGUAGE_SESSION_KEY): + request.session[LANGUAGE_SESSION_KEY] = select_language elif request.method == 'GET' and not fallback: # No session is active. We need to set a cookie for the language # so that it persists when users change their location to somewhere From 67bb54403fd7acb8e2197ddc95850710d7829a27 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 27 Apr 2016 14:10:03 +0200 Subject: [PATCH 379/654] flake8 clean everything --- feincms/extensions/translations.py | 4 ++-- feincms/module/medialibrary/modeladmins.py | 1 - feincms/module/page/models.py | 8 ++++---- feincms/utils/__init__.py | 5 +++++ 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index d02d31337..bd696320f 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -49,8 +49,8 @@ def user_has_language_set(request): This is taken later on as an indication that we should not mess with the site's language settings, after all, the user's decision is what counts. """ - if (hasattr(request, 'session') - and request.session.get(LANGUAGE_SESSION_KEY) is not None): + if (hasattr(request, 'session') and + request.session.get(LANGUAGE_SESSION_KEY) is not None): return True if LANGUAGE_COOKIE_NAME in request.COOKIES: return True diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 8f7b5f02d..8d58b6eb5 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -123,7 +123,6 @@ class MediaFileAdmin(ExtensionModelAdmin): def get_urls(self): from django.conf.urls import url - urls = super(MediaFileAdmin, self).get_urls() return [ url( r'^mediafile-bulk-upload/$', diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index ba3151662..933fd5822 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -4,9 +4,6 @@ from __future__ import absolute_import, unicode_literals -import re - -from django.apps import apps from django.core.exceptions import PermissionDenied from django.db import models from django.db.models import Q @@ -21,7 +18,10 @@ from feincms.module.mixins import ContentModelMixin from feincms.module.page import processors from feincms.utils.managers import ActiveAwareContentManagerMixin -from feincms.utils import shorten_string, match_model_string, get_model_instance +from feincms.utils import ( + shorten_string, match_model_string, get_model_instance +) + # ------------------------------------------------------------------------ class BasePageManager(ActiveAwareContentManagerMixin, TreeManager): diff --git a/feincms/utils/__init__.py b/feincms/utils/__init__.py index 28c6b69d3..f1a697a1e 100644 --- a/feincms/utils/__init__.py +++ b/feincms/utils/__init__.py @@ -15,6 +15,7 @@ from feincms import settings + # ------------------------------------------------------------------------ def get_object(path, fail_silently=False): # Return early if path isn't a string (might already be an callable or @@ -34,6 +35,7 @@ def get_object(path, fail_silently=False): if not fail_silently: raise + # ------------------------------------------------------------------------ def get_model_instance(app_label, model_name, pk): """ @@ -56,10 +58,12 @@ def get_model_instance(app_label, model_name, pk): return None + # ------------------------------------------------------------------------ REDIRECT_TO_RE = re.compile( r'^(?P\w+).(?P\w+):(?P\d+)$') + def match_model_string(s): """ Try to parse a string in format "app_label.model_name:pk", as is used @@ -75,6 +79,7 @@ def match_model_string(s): matches = match.groupdict() return (matches['app_label'], matches['model_name'], int(matches['pk'])) + # ------------------------------------------------------------------------ def copy_model_instance(obj, exclude=None): """ From 0fae5757aa976723ef7fd1e81311386ebc400c80 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 11 May 2016 14:27:08 +0200 Subject: [PATCH 380/654] Update the release notes --- docs/releases/1.12.rst | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/releases/1.12.rst b/docs/releases/1.12.rst index 3622d2f97..4b41d3506 100644 --- a/docs/releases/1.12.rst +++ b/docs/releases/1.12.rst @@ -6,11 +6,10 @@ Welcome to FeinCMS 1.12! .. warning:: - This is a cleanup release. Lots of changes ahead! + This is a cleanup release. Lots of changes ahead! Please report problems + in our issue tracker on Github_! - -Simplification of import paths -============================== +.. _Github: https://github.com/feincms/feincms/issues Template content requires explicit list of templates @@ -56,10 +55,18 @@ Also, you need to add a model migration which renames the old The blog module has been completely removed ============================================ +If you need a blog, have a look at Elephantblog_ instead. + +.. _Elephantblog: https://github.com/feincms/feincms-elephantblog + Caching of pages in various page manager methods has been removed ================================================================= +Some methods such as `Page.objects.for_request` automatically cached +the page instances. This behavior lead to non-obvious problems and has +therefore been removed. + Backwards-incompatible changes ============================== @@ -86,8 +93,7 @@ South is not supported anymore? Django 1.7 and better only? New deprecations ================ -* VideoContent, SectionContent, ContactForm?, old import paths -* Point 1 +* None. Notable features and improvements @@ -103,7 +109,7 @@ Notable features and improvements Bugfixes ======== -* Bug fix 1 +* Too many to list. Compatibility with Django and other apps From 776f569e53b6af445dd9194ce6cd05b14cf5a218 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 11 May 2016 14:27:51 +0200 Subject: [PATCH 381/654] FeinCMS v1.12.0 --- AUTHORS | 99 +++++++++++++++++++++++---------------------- feincms/__init__.py | 2 +- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/AUTHORS b/AUTHORS index 5f6987dd0..c2307127a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,103 +10,104 @@ The authors of FeinCMS are: * Bjorn Post * Julien Phalip * Daniel Renz -* Matt Dawson * Simon Schürpf -* Skylar Saveland +* Matt Dawson * Stefan Reinhard +* Skylar Saveland * Peter Schmidt * Marc Egli * Psyton * Simon Schmid * Greg Turner +* saboter * Charlie Denton -* Maarten van Gompel (proycon) * Bjarni Thorisson +* Maarten van Gompel (proycon) * Greg Taylor -* Jonas -* Urs Breton * Antoni Aloy * Julian Bez -* Vítor Figueiró -* Fabian Germann +* Urs Breton +* Jonas * Marc Tamlyn -* Martin Mahner -* Max Peterson -* Nico Echaniz +* Vítor Figueiró * Sander van Leeuwen * Toby White +* Nico Echaniz * Afonso Fernández Nogueira +* Max Peterson +* Fabian Germann +* Martin Mahner +* Eric Delord +* Andrew D. Ball * Brian Macdonald -* Torkn * Emmanuelle Delescolle -* adsworth -* Maarten Draijer * Gabriel Kovacs -* Eric Delord -* Andrew D. Ball +* Maarten Draijer +* Torkn +* adsworth +* Vaclav Klecanda * Michael Kutý -* Mikhail Korobov -* valmynd * Wil Tan +* Raphael Jasjukaitis +* Cellarosi Marco +* valmynd +* Mikhail Korobov +* Richard A * Maciek Szczesniak +* Håvard Grimelid +* Fabian Vogler * Marco Fucci * Perry Roper +* tayg * Denis Popov -* Cellarosi Marco -* Raphael Jasjukaitis -* Håvard Grimelid -* Richard A * Michael Bashkirov -* tayg -* Fabian Vogler -* Vaclav Klecanda +* Piet Delport +* Denis Martinez * Riccardo Coroneo * Richard Bolt * Rico Moorman -* svleeuwen +* David Evans * Saurabh Kumar * Sebastian Hillig * Silvan Spross +* Darryl Woods +* Daniele Procida +* Dan Büschlen * Artur Barseghyan -* Harro van der Klauw +* Anshuman Bhaduri * Andrin Heusser -* Andrey Popelo -* Andi Albrecht -* Alex Kamedov * Sumit Datta * Sun Liwen * Tobias Haffner -* Alen Mujezinovic +* Andrey Popelo +* Andi Albrecht * Valtron +* Alex Kamedov * Wim Feijen * Wouter van der Graaf * antiflu * feczo -* George Karpenkov -* Giorgos Logiotatidis -* Erik Stein -* Gwildor Sok -* Anshuman Bhaduri -* Jay Yu -* Jimmy Ye +* i-trofimtschuk +* ilmarsm +* niklaushug +* Alen Mujezinovic +* sperrygrove * Jonas Svensson -* Domas Lapinskas -* Kevin Etienne +* Jimmy Ye * Laurent Paoletti +* Kevin Etienne * Livio Lunin -* Denis Martinez -* David Evans -* i-trofimtschuk +* svleeuwen +* Jay Yu +* Harro van der Klauw * Marco Cellarosi * Mark Renton -* Darryl Woods -* ilmarsm +* Gwildor Sok +* Giorgos Logiotatidis * Mason Hugus -* Daniele Procida -* Dan Büschlen -* niklaushug +* George Karpenkov +* Erik Stein * Mikkel Hoegh -* sperrygrove +* Domas Lapinskas * Olekasnadr Gula * Paul Garner -* Piet Delport diff --git a/feincms/__init__.py b/feincms/__init__.py index 0426f87a7..959a28bce 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 12, 0, 'pre') +VERSION = (1, 12, 0) __version__ = '.'.join(map(str, VERSION)) From 30d9bd466612d79473eb883cc0d4c6528819c572 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 12 May 2016 14:05:50 +0200 Subject: [PATCH 382/654] Fix #630: Add feincms_parentlink back (was removed by accident) --- feincms/templatetags/feincms_page_tags.py | 15 +++++++++++++++ tests/testapp/tests/test_page.py | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py index b5d590818..ae43c573a 100644 --- a/feincms/templatetags/feincms_page_tags.py +++ b/feincms/templatetags/feincms_page_tags.py @@ -494,3 +494,18 @@ def page_is_active(context, page, feincms_page=None, path=None): if feincms_page is None: feincms_page = context['feincms_page'] return page.is_ancestor_of(feincms_page, include_self=True) + + +# ------------------------------------------------------------------------ +@register.simple_tag +def feincms_parentlink(of_, feincms_page, **kwargs): + level = int(kwargs.get('level', 1)) + if feincms_page.level + 1 == level: + return feincms_page.get_absolute_url() + elif feincms_page.level + 1 < level: + return '#' + + try: + return feincms_page.get_ancestors()[level - 1].get_absolute_url() + except IndexError: + return '#' diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 52a9051b6..863424db5 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -653,6 +653,11 @@ def test_17_page_template_tags(self): context = template.Context({'feincms_page': page2, 'page3': page3}) + t = template.Template( + '{% load feincms_page_tags %}{% feincms_parentlink of feincms_page' + ' level=1 %}') + self.assertEqual(t.render(context), '/test-page/') + t = template.Template( '{% load feincms_page_tags %}{% feincms_languagelinks for' ' feincms_page as links %}{% for key, name, link in links %}' From 17edc4dbfad5e4267042ca4220d82e797592e073 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 12 May 2016 14:06:33 +0200 Subject: [PATCH 383/654] FeinCMS v1.12.1 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 959a28bce..899264667 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 12, 0) +VERSION = (1, 12, 1) __version__ = '.'.join(map(str, VERSION)) From 409669dee9a148e3d6bfd0bd0f5ed90942990f6d Mon Sep 17 00:00:00 2001 From: Sebastian Walter Date: Fri, 5 Aug 2016 16:21:53 +0200 Subject: [PATCH 384/654] partial fix to #636 (works now for Django 1.10) --- feincms/contrib/fields.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index a2f207527..a39c36a58 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -29,8 +29,7 @@ def clean(self, value, *args, **kwargs): return super(JSONFormField, self).clean(value, *args, **kwargs) - -class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)): +class JSONField(models.TextField): """ TextField which transparently serializes/unserializes JSON objects @@ -61,6 +60,9 @@ def to_python(self, value): assert value is None return {} + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) + def get_prep_value(self, value): """Convert our JSON object to a string before we save""" return self._flatten_value(value) From 19d05f8d8373700ff21e5a376d4f7cdfb8971e70 Mon Sep 17 00:00:00 2001 From: Sebastian Walter Date: Fri, 5 Aug 2016 17:47:25 +0200 Subject: [PATCH 385/654] Problem: django.get_version() < '1.8' does not work for versions >= 1.10 Solution: use distutils.version.LooseVersion to compare version strings --- feincms/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 899264667..748a31680 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -58,7 +58,9 @@ def ensure_completely_loaded(force=False): # that relations defined after all content types registrations # don't miss out. import django - if django.get_version() < '1.8': + from distutils.version import LooseVersion + + if LooseVersion(django.get_version()) < LooseVersion('1.8'): for model in apps.get_models(): for cache_name in ( From 4260f8fd0f041e9d14cff18acda5066d208a9c2c Mon Sep 17 00:00:00 2001 From: Sebastian Walter Date: Fri, 5 Aug 2016 18:50:56 +0200 Subject: [PATCH 386/654] Problem: context_instance is deprecated in render_to_response Solution: store values in context --- feincms/content/contactform/models.py | 4 ++-- feincms/content/file/models.py | 5 +++-- feincms/content/filer/models.py | 2 +- feincms/content/image/models.py | 6 ++++-- feincms/content/richtext/models.py | 5 +++-- feincms/content/template/models.py | 5 +++-- feincms/content/video/models.py | 5 ++--- feincms/module/medialibrary/contents.py | 2 +- feincms/module/medialibrary/modeladmins.py | 2 +- feincms/shortcuts.py | 2 +- 10 files changed, 21 insertions(+), 17 deletions(-) diff --git a/feincms/content/contactform/models.py b/feincms/content/contactform/models.py index 35cb2f6b1..c927dbc77 100644 --- a/feincms/content/contactform/models.py +++ b/feincms/content/contactform/models.py @@ -46,7 +46,7 @@ def process(self, request, **kwargs): if request.GET.get('_cf_thanks'): self.rendered_output = render_to_string( 'content/contactform/thanks.html', - context_instance=RequestContext(request)) + request=request) return if request.method == 'POST': @@ -77,7 +77,7 @@ def process(self, request, **kwargs): 'content': self, 'form': form, }, - context_instance=RequestContext(request)) + request=request) def render(self, **kwargs): return getattr(self, 'rendered_output', '') diff --git a/feincms/content/file/models.py b/feincms/content/file/models.py index 5b86d9f95..19d362b31 100644 --- a/feincms/content/file/models.py +++ b/feincms/content/file/models.py @@ -29,11 +29,12 @@ class Meta: verbose_name_plural = _('files') def render(self, **kwargs): + context = kwargs.get('context') + context.update({'content': self}) return render_to_string( [ 'content/file/%s.html' % self.region, 'content/file/default.html', ], - {'content': self}, - context_instance=kwargs.get('context'), + context, ) diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py index 634dd4ba7..1a63a2c59 100644 --- a/feincms/content/filer/models.py +++ b/feincms/content/filer/models.py @@ -41,7 +41,7 @@ def render(self, **kwargs): 'content/filer/%s.html' % self.type, 'content/filer/%s.html' % self.file_type, 'content/filer/default.html', - ], ctx, context_instance=kwargs.get('context')) + ], ctx) class FilerFileContent(ContentWithFilerFile): mediafile = FilerFileField(verbose_name=_('file'), related_name='+') diff --git a/feincms/content/image/models.py b/feincms/content/image/models.py index 69ff2da2d..c3f92162a 100644 --- a/feincms/content/image/models.py +++ b/feincms/content/image/models.py @@ -59,10 +59,12 @@ def render(self, **kwargs): templates = ['content/image/default.html'] if hasattr(self, 'position'): templates.insert(0, 'content/image/%s.html' % self.position) + + ctx = {'content': self} + ctx.update(kwargs) return render_to_string( templates, - {'content': self}, - context_instance=kwargs.get('context'), + ctx, ) def get_image(self): diff --git a/feincms/content/richtext/models.py b/feincms/content/richtext/models.py index e32ee4c09..94cc73ff3 100644 --- a/feincms/content/richtext/models.py +++ b/feincms/content/richtext/models.py @@ -34,10 +34,11 @@ class Meta: verbose_name_plural = _('rich texts') def render(self, **kwargs): + ctx = {'content': self} + ctx.update(kwargs) return render_to_string( 'content/richtext/default.html', - {'content': self}, - context_instance=kwargs.get('context')) + ctx) @classmethod def initialize_type(cls, cleanse=None): diff --git a/feincms/content/template/models.py b/feincms/content/template/models.py index 1060e1235..506cd017e 100644 --- a/feincms/content/template/models.py +++ b/feincms/content/template/models.py @@ -33,7 +33,8 @@ def initialize_type(cls, TEMPLATES): )) def render(self, **kwargs): + ctx = {'content': self} + ctx.update(kwargs) return render_to_string( self.template, - {'content': self}, - context_instance=kwargs.get('context')) + ctx) diff --git a/feincms/content/video/models.py b/feincms/content/video/models.py index b650aa21a..3f00480dd 100644 --- a/feincms/content/video/models.py +++ b/feincms/content/video/models.py @@ -66,9 +66,8 @@ def ctx_for_video(self, vurl): return ctx def render(self, **kwargs): - context_instance = kwargs.get('context') ctx = self.ctx_for_video(self.video) + ctx.update(kwargs) return render_to_string( self.get_templates(ctx['portal']), - ctx, - context_instance=context_instance) + ctx) diff --git a/feincms/module/medialibrary/contents.py b/feincms/module/medialibrary/contents.py index 3c6bab81c..98ba6f6a2 100644 --- a/feincms/module/medialibrary/contents.py +++ b/feincms/module/medialibrary/contents.py @@ -72,4 +72,4 @@ def render(self, **kwargs): 'content/mediafile/%s.html' % self.mediafile.type, 'content/mediafile/%s.html' % self.type, 'content/mediafile/default.html', - ], ctx, context_instance=kwargs.get('context')) + ], ctx) diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 8d58b6eb5..102d22674 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -78,7 +78,7 @@ class AddCategoryForm(forms.Form): 'mediafiles': queryset, 'category_form': form, 'opts': modeladmin.model._meta, - }, context_instance=RequestContext(request)) + }, request=request) assign_category.short_description = _('Add selected media files to category') diff --git a/feincms/shortcuts.py b/feincms/shortcuts.py index adb47aa4e..1f72b27db 100644 --- a/feincms/shortcuts.py +++ b/feincms/shortcuts.py @@ -17,4 +17,4 @@ def render_to_response_best_match(request, template_name, dictionary=None): return render_to_response( template_name, dictionary, - context_instance=RequestContext(request)) + request=request) From c18dd93627d1d7a3875525166acea5f4de38032e Mon Sep 17 00:00:00 2001 From: Sebastian Walter Date: Mon, 8 Aug 2016 09:48:27 +0200 Subject: [PATCH 387/654] Problem: feincms should be compatible with Django >= 1.8 but API has changed Solution: use django.get_version See: https://github.com/feincms/feincms/issues/636#issuecomment-238010313 --- feincms/contrib/fields.py | 10 +++++++++- tests/tox.ini | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index a39c36a58..2066133ef 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -2,7 +2,9 @@ import json import logging +from distutils.version import LooseVersion +from django import get_version from django import forms from django.db import models from django.core.serializers.json import DjangoJSONEncoder @@ -29,7 +31,13 @@ def clean(self, value, *args, **kwargs): return super(JSONFormField, self).clean(value, *args, **kwargs) -class JSONField(models.TextField): + +if LooseVersion(get_version()) > LooseVersion('1.8'): + workaround_class = models.TextField +else: + workaround_class = six.with_metaclass(models.SubfieldBase, models.TextField) + +class JSONField(workaround_class): """ TextField which transparently serializes/unserializes JSON objects diff --git a/tests/tox.ini b/tests/tox.ini index 2b7ced15e..69f4a8a20 100644 --- a/tests/tox.ini +++ b/tests/tox.ini @@ -5,6 +5,8 @@ envlist = {py26,py27,py32,py33,py34}-django16, {py27,py33,py34}-django17, {py27,py34}-django18, + {py27,py34}-django19 + [testenv] commands = @@ -15,6 +17,7 @@ deps = django16: Django>=1.6,<1.7 django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 + django19: Django>=1.9,<1.10 feedparser==5.1.3 Pillow==2.4.0 pytz==2014.10 @@ -24,3 +27,4 @@ deps = {py27,py34}-django18: django-mptt==0.7.1 {py26,py27}-django{16,17,18}: BeautifulSoup==3.2.1 {py32,py33,py34}-django{16,17,18}: beautifulsoup4==4.3.1 + {py27,py34}-django{19}: django-mptt==0.8.5 From 2939d6aeae7990f3fbc45cd97273319c0c2a116d Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 10:41:41 +0200 Subject: [PATCH 388/654] Add a test which verifies that context processor's values are available in content type's templates --- tests/testapp/context_processors.py | 2 ++ tests/testapp/settings.py | 1 + tests/testapp/templates/feincms_base.html | 1 + tests/testapp/templates/templatecontent_1.html | 1 + tests/testapp/tests/test_page.py | 18 ++++++++++++++---- 5 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 tests/testapp/context_processors.py diff --git a/tests/testapp/context_processors.py b/tests/testapp/context_processors.py new file mode 100644 index 000000000..7d0253c20 --- /dev/null +++ b/tests/testapp/context_processors.py @@ -0,0 +1,2 @@ +def test_context(request): + return {'THE_ANSWER': 42} diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index d6e2612ac..36c764ec9 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -44,6 +44,7 @@ 'django.core.context_processors.static', # request context processor is needed 'django.core.context_processors.request', + 'testapp.context_processors.test_context', ) MIDDLEWARE_CLASSES = ( diff --git a/tests/testapp/templates/feincms_base.html b/tests/testapp/templates/feincms_base.html index 3cac88c3a..78f055f6b 100644 --- a/tests/testapp/templates/feincms_base.html +++ b/tests/testapp/templates/feincms_base.html @@ -1 +1,2 @@ +{% extends "base.html" %} {# only has to exist #} diff --git a/tests/testapp/templates/templatecontent_1.html b/tests/testapp/templates/templatecontent_1.html index f2194550e..b2986f579 100644 --- a/tests/testapp/templates/templatecontent_1.html +++ b/tests/testapp/templates/templatecontent_1.html @@ -1 +1,2 @@ TemplateContent_1 +#{{ THE_ANSWER }}# diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 863424db5..900618aca 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1659,14 +1659,14 @@ def test_40_page_is_active(self): {'feincms_page': page2}, p, path='/test-page/')) def test_41_templatecontent(self): - page = self.create_page() + page = self.create_page(active=True) template = page.templatecontent_set.create( - region=0, - ordering=1, + region='main', + ordering=10, template='templatecontent_1.html', ) - self.assertEqual(template.render(), 'TemplateContent_1\n') + self.assertEqual(template.render(), 'TemplateContent_1\n##\n') # The empty form contains the template option. self.login() @@ -1675,3 +1675,13 @@ def test_41_templatecontent(self): reverse('admin:page_page_change', args=(page.id,)) ), '') + + response = self.client.get(page.get_absolute_url()) + self.assertContains( + response, + 'TemplateContent_1', + ) + self.assertContains( + response, + '#42#', + ) From 59398ac57e4f0ea72c3d65e645533858d68a1d4e Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 11:02:11 +0200 Subject: [PATCH 389/654] Hopefully adapt correctly to the render_to_string API change --- feincms/_internal.py | 24 ++++++++++++++++++++++ feincms/content/contactform/models.py | 8 +++++--- feincms/content/file/models.py | 10 ++++----- feincms/content/filer/models.py | 21 +++++++++++-------- feincms/content/image/models.py | 10 ++++----- feincms/content/richtext/models.py | 11 +++++----- feincms/content/section/models.py | 6 ++++-- feincms/content/template/models.py | 11 +++++----- feincms/content/video/models.py | 11 ++++++---- feincms/contrib/fields.py | 4 +++- feincms/module/medialibrary/contents.py | 22 ++++++++++++-------- feincms/module/medialibrary/modeladmins.py | 1 - feincms/shortcuts.py | 8 ++------ tests/testapp/tests/test_page.py | 2 +- 14 files changed, 93 insertions(+), 56 deletions(-) diff --git a/feincms/_internal.py b/feincms/_internal.py index fe0f72fa7..94041bcb9 100644 --- a/feincms/_internal.py +++ b/feincms/_internal.py @@ -6,6 +6,10 @@ from __future__ import absolute_import, unicode_literals +from distutils.version import LooseVersion +from django import get_version +from django.template.loader import render_to_string + __all__ = ( 'monkeypatch_method', 'monkeypatch_property', @@ -40,3 +44,23 @@ def decorator(func): setattr(cls, func.__name__, property(func)) return func return decorator + + +if LooseVersion(get_version()) < LooseVersion('1.10'): + def ct_render_to_string(template, ctx, **kwargs): + from django.template import RequestContext + + context_instance = kwargs.get('context') + if context_instance is None and 'request' in kwargs: + context_instance = RequestContext(kwargs['request']) + + return render_to_string( + template, + ctx, + context_instance=context_instance) +else: + def ct_render_to_string(template, ctx, **kwargs): + return render_to_string( + template, + ctx, + request=kwargs.get('request')) diff --git a/feincms/content/contactform/models.py b/feincms/content/contactform/models.py index c927dbc77..cfda97377 100644 --- a/feincms/content/contactform/models.py +++ b/feincms/content/contactform/models.py @@ -11,10 +11,11 @@ from django.core.mail import send_mail from django.db import models from django.http import HttpResponseRedirect -from django.template import RequestContext from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ +from feincms._internal import ct_render_to_string + class ContactForm(forms.Form): name = forms.CharField(label=_('name')) @@ -44,8 +45,9 @@ def initialize_type(cls, form=None): def process(self, request, **kwargs): if request.GET.get('_cf_thanks'): - self.rendered_output = render_to_string( + self.rendered_output = ct_render_to_string( 'content/contactform/thanks.html', + {'content': self}, request=request) return @@ -72,7 +74,7 @@ def process(self, request, **kwargs): form = self.form(initial=initial) - self.rendered_output = render_to_string( + self.rendered_output = ct_render_to_string( 'content/contactform/form.html', { 'content': self, 'form': form, diff --git a/feincms/content/file/models.py b/feincms/content/file/models.py index 19d362b31..fda078a9f 100644 --- a/feincms/content/file/models.py +++ b/feincms/content/file/models.py @@ -8,10 +8,10 @@ import os from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from feincms import settings +from feincms._internal import ct_render_to_string class FileContent(models.Model): @@ -29,12 +29,12 @@ class Meta: verbose_name_plural = _('files') def render(self, **kwargs): - context = kwargs.get('context') - context.update({'content': self}) - return render_to_string( + return ct_render_to_string( [ 'content/file/%s.html' % self.region, 'content/file/default.html', ], - context, + {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), ) diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py index 1a63a2c59..f728f0fa1 100644 --- a/feincms/content/filer/models.py +++ b/feincms/content/filer/models.py @@ -3,10 +3,10 @@ from django.contrib import admin from django.core.exceptions import ImproperlyConfigured from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from feincms.admin.item_editor import FeinCMSInline +from feincms._internal import ct_render_to_string try: from filer.fields.file import FilerFileField @@ -34,14 +34,17 @@ class Meta: abstract = True def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string([ - 'content/filer/%s_%s.html' % (self.file_type, self.type), - 'content/filer/%s.html' % self.type, - 'content/filer/%s.html' % self.file_type, - 'content/filer/default.html', - ], ctx) + return ct_render_to_string( + [ + 'content/filer/%s_%s.html' % (self.file_type, self.type), + 'content/filer/%s.html' % self.type, + 'content/filer/%s.html' % self.file_type, + 'content/filer/default.html', + ], + {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), + ) class FilerFileContent(ContentWithFilerFile): mediafile = FilerFileField(verbose_name=_('file'), related_name='+') diff --git a/feincms/content/image/models.py b/feincms/content/image/models.py index c3f92162a..170236a92 100644 --- a/feincms/content/image/models.py +++ b/feincms/content/image/models.py @@ -8,10 +8,10 @@ import os from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from feincms import settings +from feincms._internal import ct_render_to_string from feincms.templatetags import feincms_thumbnail @@ -60,11 +60,11 @@ def render(self, **kwargs): if hasattr(self, 'position'): templates.insert(0, 'content/image/%s.html' % self.position) - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string( + return ct_render_to_string( templates, - ctx, + {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), ) def get_image(self): diff --git a/feincms/content/richtext/models.py b/feincms/content/richtext/models.py index 94cc73ff3..8ad5a1e9c 100644 --- a/feincms/content/richtext/models.py +++ b/feincms/content/richtext/models.py @@ -1,10 +1,10 @@ from __future__ import absolute_import, unicode_literals from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from feincms import settings +from feincms._internal import ct_render_to_string from feincms.contrib.richtext import RichTextField @@ -34,11 +34,12 @@ class Meta: verbose_name_plural = _('rich texts') def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string( + return ct_render_to_string( 'content/richtext/default.html', - ctx) + {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), + ) @classmethod def initialize_type(cls, cleanse=None): diff --git a/feincms/content/section/models.py b/feincms/content/section/models.py index 88516290a..5771c2fd1 100644 --- a/feincms/content/section/models.py +++ b/feincms/content/section/models.py @@ -4,10 +4,10 @@ from django.contrib import admin from django.core.exceptions import ImproperlyConfigured from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ from feincms import settings +from feincms._internal import ct_render_to_string from feincms.admin.item_editor import FeinCMSInline from feincms.contrib.richtext import RichTextField from feincms.module.medialibrary.fields import MediaFileForeignKey @@ -76,7 +76,7 @@ def render(self, **kwargs): else: mediafile_type = 'nomedia' - return render_to_string( + return ct_render_to_string( [ 'content/section/%s_%s.html' % (mediafile_type, self.type), 'content/section/%s.html' % mediafile_type, @@ -84,6 +84,8 @@ def render(self, **kwargs): 'content/section/default.html', ], {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), ) def save(self, *args, **kwargs): diff --git a/feincms/content/template/models.py b/feincms/content/template/models.py index 506cd017e..26ec26e44 100644 --- a/feincms/content/template/models.py +++ b/feincms/content/template/models.py @@ -1,9 +1,9 @@ from __future__ import absolute_import, unicode_literals from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ +from feincms._internal import ct_render_to_string from feincms.content.raw.models import RawContent # noqa from feincms.content.richtext.models import RichTextContent # noqa @@ -33,8 +33,9 @@ def initialize_type(cls, TEMPLATES): )) def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string( + return ct_render_to_string( self.template, - ctx) + {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), + ) diff --git a/feincms/content/video/models.py b/feincms/content/video/models.py index 3f00480dd..32a4cae81 100644 --- a/feincms/content/video/models.py +++ b/feincms/content/video/models.py @@ -3,9 +3,10 @@ import re from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ +from feincms._internal import ct_render_to_string + class VideoContent(models.Model): """ @@ -67,7 +68,9 @@ def ctx_for_video(self, vurl): def render(self, **kwargs): ctx = self.ctx_for_video(self.video) - ctx.update(kwargs) - return render_to_string( + return ct_render_to_string( self.get_templates(ctx['portal']), - ctx) + ctx, + request=kwargs.get('request'), + context=kwargs.get('context'), + ) diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index 2066133ef..37af3bdec 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -35,7 +35,9 @@ def clean(self, value, *args, **kwargs): if LooseVersion(get_version()) > LooseVersion('1.8'): workaround_class = models.TextField else: - workaround_class = six.with_metaclass(models.SubfieldBase, models.TextField) + workaround_class = six.with_metaclass( + models.SubfieldBase, models.TextField) + class JSONField(workaround_class): """ diff --git a/feincms/module/medialibrary/contents.py b/feincms/module/medialibrary/contents.py index 98ba6f6a2..b5b2cd435 100644 --- a/feincms/module/medialibrary/contents.py +++ b/feincms/module/medialibrary/contents.py @@ -3,9 +3,9 @@ from django.contrib import admin from django.core.exceptions import ImproperlyConfigured from django.db import models -from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ +from feincms._internal import ct_render_to_string from feincms.admin.item_editor import FeinCMSInline from feincms.module.medialibrary.fields import ContentWithMediaFile @@ -65,11 +65,15 @@ def initialize_type(cls, TYPE_CHOICES=None): ) def render(self, **kwargs): - ctx = {'content': self} - ctx.update(kwargs) - return render_to_string([ - 'content/mediafile/%s_%s.html' % (self.mediafile.type, self.type), - 'content/mediafile/%s.html' % self.mediafile.type, - 'content/mediafile/%s.html' % self.type, - 'content/mediafile/default.html', - ], ctx) + return ct_render_to_string( + [ + 'content/mediafile/%s_%s.html' % ( + self.mediafile.type, self.type), + 'content/mediafile/%s.html' % self.mediafile.type, + 'content/mediafile/%s.html' % self.type, + 'content/mediafile/default.html', + ], + {'content': self}, + request=kwargs.get('request'), + context=kwargs.get('context'), + ) diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 102d22674..263d7d88f 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -16,7 +16,6 @@ from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.shortcuts import render_to_response -from django.template.context import RequestContext from django.template.defaultfilters import filesizeformat from django.utils.safestring import mark_safe from django.utils.translation import ungettext, ugettext_lazy as _ diff --git a/feincms/shortcuts.py b/feincms/shortcuts.py index 1f72b27db..936460915 100644 --- a/feincms/shortcuts.py +++ b/feincms/shortcuts.py @@ -1,7 +1,6 @@ from __future__ import absolute_import, unicode_literals -from django.shortcuts import render_to_response -from django.template import RequestContext +from django.shortcuts import render from feincms.module.page.models import Page @@ -14,7 +13,4 @@ def render_to_response_best_match(request, template_name, dictionary=None): dictionary = dictionary or {} dictionary['feincms_page'] = Page.objects.best_match_for_request(request) - return render_to_response( - template_name, - dictionary, - request=request) + return render(request, template_name, dictionary) diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 900618aca..aac548ee4 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1666,7 +1666,7 @@ def test_41_templatecontent(self): template='templatecontent_1.html', ) - self.assertEqual(template.render(), 'TemplateContent_1\n##\n') + self.assertEqual(template.render(), 'TemplateContent_1\n#42#\n') # The empty form contains the template option. self.login() From 78eabbb23ecd5dd4a7e5c16fe63fa091d107fa9c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 11:29:37 +0200 Subject: [PATCH 390/654] Include Django 1.10 in the Travis test matrix Refs #637, #636. --- .travis.yml | 11 ++++--- .../templatetags/applicationcontent_tags.py | 12 +++++++- setup.py | 30 +++++-------------- tests/requirements.txt | 2 +- tests/testapp/settings.py | 16 ++++++++++ tests/testapp/tests/test_page.py | 2 -- 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 151e62900..4d4d9daf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,19 @@ language: python sudo: false +cache: pip python: - "2.7" - - "3.2" - "3.4" + - "3.5" env: - REQ="Django>=1.7,<1.8 django-mptt<0.8" - REQ="Django>=1.8,<1.9 django-mptt<0.8" - REQ="Django>=1.9,<1.10 django-mptt" -matrix: - exclude: - - python: "3.2" - env: REQ="Django>=1.9,<1.10 django-mptt" + - REQ="Django>=1.10,<1.11 django-mptt-nomagic" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: + - pip install -U pip wheel setuptools - pip install $REQ Pillow feedparser flake8 - - python setup.py -q install + - python setup.py install # command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && flake8 ." diff --git a/feincms/templatetags/applicationcontent_tags.py b/feincms/templatetags/applicationcontent_tags.py index f1d5e1ea0..c698810e3 100644 --- a/feincms/templatetags/applicationcontent_tags.py +++ b/feincms/templatetags/applicationcontent_tags.py @@ -40,6 +40,16 @@ def feincms_render_region_appcontent(page, region, request): if content.region == region) +def _current_app(context): + try: + return context.request.current_app + except AttributeError: + try: + return context.request.resolver_match.namespace + except AttributeError: + return getattr(context, 'current_app', None) + + class AppReverseNode(template.Node): def __init__(self, view_name, urlconf, args, kwargs, asvar): self.view_name = view_name @@ -59,7 +69,7 @@ def render(self, context): try: url = do_app_reverse( view_name, urlconf, args=args, kwargs=kwargs, - current_app=context.current_app) + current_app=_current_app(context)) except NoReverseMatch: if self.asvar is None: raise diff --git a/setup.py b/setup.py index c8cdb5c0b..402c93c61 100755 --- a/setup.py +++ b/setup.py @@ -30,25 +30,13 @@ def read(filename): packages=find_packages( exclude=['tests'] ), - package_data={ - '': ['*.html', '*.txt'], - 'feincms': [ - 'locale/*/*/*.*', - 'static/feincms/*.*', - 'static/feincms/*/*.*', - 'templates/*.*', - 'templates/*/*.*', - 'templates/*/*/*.*', - 'templates/*/*/*/*.*', - 'templates/*/*/*/*/*.*', - ], - }, - install_requires=[ - 'Django>=1.6', - 'django-mptt>=0.7.1', - 'Pillow>=2.0.0', - 'pytz>=2014.10', - ], + include_package_data=True, + # install_requires=[ + # 'Django>=1.6', + # 'django-mptt>=0.7.1', + # 'Pillow>=2.0.0', + # 'pytz>=2014.10', + # ], classifiers=[ devstatus, 'Environment :: Web Environment', @@ -58,12 +46,10 @@ def read(filename): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries :: Application Frameworks', diff --git a/tests/requirements.txt b/tests/requirements.txt index 2c446f486..162f035fa 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,5 @@ Django -django-mptt +django-mptt-nomagic Pillow coverage pytz diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index 36c764ec9..6cb030d4a 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -47,6 +47,22 @@ 'testapp.context_processors.test_context', ) +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'testapp.context_processors.test_context', + ], + }, + }, +] MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index aac548ee4..61dbb318b 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1666,8 +1666,6 @@ def test_41_templatecontent(self): template='templatecontent_1.html', ) - self.assertEqual(template.render(), 'TemplateContent_1\n#42#\n') - # The empty form contains the template option. self.login() self.assertContains( From a670da5ae9736271255ad086223adfedd61dfbc0 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 11:37:07 +0200 Subject: [PATCH 391/654] Add pytz --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4d4d9daf6..f92ecd148 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ env: # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install -U pip wheel setuptools - - pip install $REQ Pillow feedparser flake8 + - pip install $REQ Pillow flake8 pytz - python setup.py install # command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && flake8 ." From 3aa00ba17001e9681bcbce0bacedcbc37a3e9efc Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 11:43:08 +0200 Subject: [PATCH 392/654] Remove an unused variable --- tests/testapp/tests/test_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 61dbb318b..64db945aa 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -1660,7 +1660,7 @@ def test_40_page_is_active(self): def test_41_templatecontent(self): page = self.create_page(active=True) - template = page.templatecontent_set.create( + page.templatecontent_set.create( region='main', ordering=10, template='templatecontent_1.html', From fda730dc3725dc1e26facc05e550816a28f579c7 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 11:53:30 +0200 Subject: [PATCH 393/654] Remove an unsupported combination from .travis.yml --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f92ecd148..20c8165f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,10 @@ env: - REQ="Django>=1.8,<1.9 django-mptt<0.8" - REQ="Django>=1.9,<1.10 django-mptt" - REQ="Django>=1.10,<1.11 django-mptt-nomagic" +matrix: + exclude: + - python: "3.5" + REQ="Django>=1.7,<1.8 django-mptt<0.8" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install -U pip wheel setuptools From e849cff8f65211941ff2e83a63b4ff8c631e428b Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 11:59:41 +0200 Subject: [PATCH 394/654] .travis.yml syntax fix --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 20c8165f0..f96ca3922 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ env: matrix: exclude: - python: "3.5" - REQ="Django>=1.7,<1.8 django-mptt<0.8" + env: REQ="Django>=1.7,<1.8 django-mptt<0.8" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install -U pip wheel setuptools From b7cef7165460fade1d6b9351bf687bddbb531c9a Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 12:46:03 +0200 Subject: [PATCH 395/654] Add 1.13 release notes --- docs/index.rst | 1 + docs/releases/1.13.rst | 53 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 docs/releases/1.13.rst diff --git a/docs/index.rst b/docs/index.rst index 9ecff341e..2188ee8d2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -80,6 +80,7 @@ Releases releases/1.10 releases/1.11 releases/1.12 + releases/1.13 Indices and tables diff --git a/docs/releases/1.13.rst b/docs/releases/1.13.rst new file mode 100644 index 000000000..3cb2fceca --- /dev/null +++ b/docs/releases/1.13.rst @@ -0,0 +1,53 @@ +========================== +FeinCMS 1.13 release notes +========================== + +Welcome to FeinCMS 1.13! + + +Compatibility with Django 1.10 +============================== + +The biggest feature of Django 1.10 is being compatible with Django 1.10. +Please note that django-mptt_ is at the time of writing not compatible +with Django 1.10 yet. You may want to try django-mptt-nomagic_. + + +Backwards-incompatible changes +============================== + +* None. + + +Removal of deprecated features +------------------------------ + +* None. + + +New deprecations +================ + +* None. + + +Notable features and improvements +================================= + +* Some support has been added for ``django-filer``. + + +Bugfixes +======== + +* Too many to list. + + +Compatibility with Django and other apps +======================================== + +FeinCMS 1.13 requires Django 1.7 or better. + + +.. _django-mptt: https://github.com/django-mptt/django-mptt +.. _django-mptt-nomagic: https://pypi.python.org/pypi/django-mptt-nomagic/ From f21f95d13ed0325cb4b4262b3365faadd4faadbd Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 8 Aug 2016 17:30:36 +0200 Subject: [PATCH 396/654] Re-generate AUTHORS, maybe I'll forget to do that later --- AUTHORS | 107 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/AUTHORS b/AUTHORS index c2307127a..0d8f2f02a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,108 +6,109 @@ The authors of FeinCMS are: * Simon Meers * Bojan Mihelac * Simon Bächler -* Stephan Jaekel * Bjorn Post +* Stephan Jaekel * Julien Phalip * Daniel Renz -* Simon Schürpf -* Matt Dawson * Stefan Reinhard +* Matt Dawson +* Simon Schürpf * Skylar Saveland * Peter Schmidt * Marc Egli * Psyton * Simon Schmid * Greg Turner -* saboter * Charlie Denton -* Bjarni Thorisson -* Maarten van Gompel (proycon) +* saboter * Greg Taylor +* Maarten van Gompel (proycon) +* Bjarni Thorisson * Antoni Aloy * Julian Bez * Urs Breton * Jonas -* Marc Tamlyn * Vítor Figueiró +* Fabian Germann +* Marc Tamlyn +* Martin Mahner +* Max Peterson +* Nico Echaniz * Sander van Leeuwen +* Sebastian Walter * Toby White -* Nico Echaniz * Afonso Fernández Nogueira -* Max Peterson -* Fabian Germann -* Martin Mahner -* Eric Delord -* Andrew D. Ball * Brian Macdonald +* Eric Delord * Emmanuelle Delescolle -* Gabriel Kovacs * Maarten Draijer -* Torkn * adsworth -* Vaclav Klecanda -* Michael Kutý +* Torkn +* Andrew D. Ball +* Gabriel Kovacs * Wil Tan -* Raphael Jasjukaitis -* Cellarosi Marco -* valmynd -* Mikhail Korobov -* Richard A -* Maciek Szczesniak -* Håvard Grimelid -* Fabian Vogler -* Marco Fucci * Perry Roper -* tayg +* Marco Fucci * Denis Popov +* Fabian Vogler +* Cellarosi Marco +* Raphael Jasjukaitis * Michael Bashkirov -* Piet Delport -* Denis Martinez -* Riccardo Coroneo +* Håvard Grimelid +* Richard A +* Michael Kutý +* Mikhail Korobov +* tayg +* Maciek Szczesniak +* Vaclav Klecanda +* valmynd * Richard Bolt * Rico Moorman -* David Evans +* sperrygrove * Saurabh Kumar * Sebastian Hillig +* svleeuwen * Silvan Spross -* Darryl Woods -* Daniele Procida -* Dan Büschlen * Artur Barseghyan -* Anshuman Bhaduri +* Jay Yu * Andrin Heusser +* Andrey Popelo +* Andi Albrecht * Sumit Datta * Sun Liwen * Tobias Haffner -* Andrey Popelo -* Andi Albrecht -* Valtron * Alex Kamedov +* Valtron * Wim Feijen * Wouter van der Graaf * antiflu * feczo -* i-trofimtschuk -* ilmarsm -* niklaushug -* Alen Mujezinovic -* sperrygrove -* Jonas Svensson +* George Karpenkov +* Giorgos Logiotatidis +* Erik Stein +* Gwildor Sok +* Harro van der Klauw +* Anshuman Bhaduri * Jimmy Ye -* Laurent Paoletti +* Jonas Svensson +* Domas Lapinskas * Kevin Etienne +* Laurent Paoletti * Livio Lunin -* svleeuwen -* Jay Yu -* Harro van der Klauw +* Denis Martinez +* i-trofimtschuk * Marco Cellarosi * Mark Renton -* Gwildor Sok -* Giorgos Logiotatidis +* David Evans +* ilmarsm * Mason Hugus -* George Karpenkov -* Erik Stein +* Darryl Woods +* Daniele Procida +* niklaushug * Mikkel Hoegh -* Domas Lapinskas +* Alen Mujezinovic * Olekasnadr Gula * Paul Garner +* Dan Büschlen +* Piet Delport +* Riccardo Coroneo From 61aa94274ed5b8f8bd92624e6fdd50b9321df58c Mon Sep 17 00:00:00 2001 From: Sebastian Walter Date: Mon, 8 Aug 2016 18:16:36 +0200 Subject: [PATCH 397/654] Problem: render_to_response has different api than render_to_string Solution: fix bug --- feincms/module/medialibrary/modeladmins.py | 6 +++--- feincms/shortcuts.py | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 102d22674..5b95429ee 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -15,7 +15,7 @@ from django.core.files.images import get_image_dimensions from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect -from django.shortcuts import render_to_response +from django.shortcuts import render from django.template.context import RequestContext from django.template.defaultfilters import filesizeformat from django.utils.safestring import mark_safe @@ -74,11 +74,11 @@ class AddCategoryForm(forms.Form): admin.ACTION_CHECKBOX_NAME), }) - return render_to_response('admin/medialibrary/add_to_category.html', { + return render(request, 'admin/medialibrary/add_to_category.html', { 'mediafiles': queryset, 'category_form': form, 'opts': modeladmin.model._meta, - }, request=request) + }) assign_category.short_description = _('Add selected media files to category') diff --git a/feincms/shortcuts.py b/feincms/shortcuts.py index 1f72b27db..157122a22 100644 --- a/feincms/shortcuts.py +++ b/feincms/shortcuts.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -from django.shortcuts import render_to_response +from django.shortcuts import render from django.template import RequestContext from feincms.module.page.models import Page @@ -14,7 +14,6 @@ def render_to_response_best_match(request, template_name, dictionary=None): dictionary = dictionary or {} dictionary['feincms_page'] = Page.objects.best_match_for_request(request) - return render_to_response( + return render(request, template_name, - dictionary, - request=request) + dictionary) From d67b536583db6f69b801040198e26d19641a2f7e Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 10 Aug 2016 10:15:33 +0200 Subject: [PATCH 398/654] request has to be truthy --- feincms/_internal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/_internal.py b/feincms/_internal.py index 94041bcb9..134046da1 100644 --- a/feincms/_internal.py +++ b/feincms/_internal.py @@ -51,7 +51,7 @@ def ct_render_to_string(template, ctx, **kwargs): from django.template import RequestContext context_instance = kwargs.get('context') - if context_instance is None and 'request' in kwargs: + if context_instance is None and kwargs.get('request'): context_instance = RequestContext(kwargs['request']) return render_to_string( From 9c65047afc261a6d9878f906b07800610a444607 Mon Sep 17 00:00:00 2001 From: Martin Pauly Date: Tue, 6 Sep 2016 21:11:34 +0200 Subject: [PATCH 399/654] Added a CSS class of subitems in TreeEditor --- feincms/admin/tree_editor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index 9774dd4f5..40a027493 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -260,12 +260,16 @@ def indented_short_title(self, item): changeable_class = '' if not self.changeable(item): changeable_class = ' tree-item-not-editable' + subitem_class = '' + if item.parent: + subitem_class = ' tree-subitem' r += ( - '  ') % ( item.pk, changeable_class, + subitem_class, 14 + getattr(item, mptt_opts.level_attr) * 18) # r += '' From 6fd217eac3f869790992c83346a91d5e8882c54a Mon Sep 17 00:00:00 2001 From: Martin Pauly Date: Fri, 9 Sep 2016 09:36:45 +0200 Subject: [PATCH 400/654] Add a tree-root class root items in the TreeEditor --- feincms/admin/tree_editor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index 40a027493..e1d288f23 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -260,16 +260,16 @@ def indented_short_title(self, item): changeable_class = '' if not self.changeable(item): changeable_class = ' tree-item-not-editable' - subitem_class = '' - if item.parent: - subitem_class = ' tree-subitem' + tree_root_class = '' + if not item.parent: + tree_root_class = ' tree-root' r += ( '  ') % ( item.pk, changeable_class, - subitem_class, + tree_root_class, 14 + getattr(item, mptt_opts.level_attr) * 18) # r += '' From 01bd0841f6fbd963aebdfa607f1f3345e0e1fb50 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Mon, 31 Oct 2016 09:34:03 +0100 Subject: [PATCH 401/654] Finally hunt down the AmbiguousTimeError that haunts us every halloween (aka DST end transition). Refs #542 --- feincms/extensions/datepublisher.py | 29 ++++++++++++++++++++++------- tests/testapp/tests/test_stuff.py | 23 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/feincms/extensions/datepublisher.py b/feincms/extensions/datepublisher.py index 48d099a02..e676ccbdd 100644 --- a/feincms/extensions/datepublisher.py +++ b/feincms/extensions/datepublisher.py @@ -11,6 +11,7 @@ from __future__ import absolute_import, unicode_literals from datetime import datetime +from pytz.exceptions import AmbiguousTimeError from django.db import models from django.db.models import Q @@ -41,7 +42,7 @@ def latest_children(self): # ------------------------------------------------------------------------ -def granular_now(n=None): +def granular_now(n=None, default_tz=None): """ A datetime.now look-alike that returns times rounded to a five minute boundary. This helps the backend database to optimize/reuse/cache its @@ -51,13 +52,27 @@ def granular_now(n=None): """ if n is None: n = timezone.now() - # WARNING/TODO: make_aware can raise a pytz NonExistentTimeError or - # AmbiguousTimeError if the resultant time is invalid in n.tzinfo - # -- see https://github.com/feincms/feincms/commit/5d0363df - return timezone.make_aware( - datetime(n.year, n.month, n.day, n.hour, (n.minute // 5) * 5), - n.tzinfo) + if default_tz is None: + default_tz = n.tzinfo + + # Django 1.9: + # The correct way to resolve the AmbiguousTimeError every dst + # transition is... the is_dst parameter appeared with 1.9 + # make_aware(some_datetime, get_current_timezone(), is_dst=True) + + rounded_minute = (n.minute // 5) * 5 + d = datetime(n.year, n.month, n.day, n.hour, rounded_minute) + try: + retval = timezone.make_aware(d, default_tz) + except AmbiguousTimeError: + try: + retval = timezone.make_aware(d, default_tz, is_dst=False) + except TypeError: # Pre-Django 1.9 + retval = timezone.make_aware( + datetime(n.year, n.month, n.day, n.hour + 1, rounded_minute), + default_tz) + return retval # ------------------------------------------------------------------------ def datepublisher_response_processor(page, request, response): diff --git a/tests/testapp/tests/test_stuff.py b/tests/testapp/tests/test_stuff.py index c5d3c94a5..7b986db67 100644 --- a/tests/testapp/tests/test_stuff.py +++ b/tests/testapp/tests/test_stuff.py @@ -6,13 +6,16 @@ import doctest +import pytz +from datetime import datetime + from django.test import TestCase from django.utils.encoding import force_text import feincms from feincms.models import Region, Template from feincms.utils import get_object, shorten_string - +from feincms.extensions.datepublisher import granular_now # ------------------------------------------------------------------------ class Empty(object): @@ -77,3 +80,21 @@ def test_shorten_string(self): 10, ellipsis='-') self.assertEqual(string, 'Badger-ger') self.assertEqual(len(string), 10) + +# ------------------------------------------------------------------------ +class TimezoneTest(TestCase): + def test_granular_now_dst_transition(self): + # Should not raise an exception + d = datetime(2016, 10, 30, 2, 10) + tz = pytz.timezone('Europe/Copenhagen') + g = granular_now(d, default_tz=tz) + self.assertEqual(d.hour + 1, g.hour) + self.assertEqual(d.minute, g.minute) + + def test_granular_now_rounding(self): + d = datetime(2016, 1, 3, 1, 13) + g = granular_now(d) + self.assertEqual(d.hour, g.hour) + self.assertEqual(10, g.minute) + +# ------------------------------------------------------------------------ From 8c6a052fd663095b779cf0492ff7ed45ee0d38fc Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Mon, 31 Oct 2016 09:59:06 +0100 Subject: [PATCH 402/654] flake8 warnings in new tests --- tests/testapp/tests/test_stuff.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testapp/tests/test_stuff.py b/tests/testapp/tests/test_stuff.py index 7b986db67..b567daba6 100644 --- a/tests/testapp/tests/test_stuff.py +++ b/tests/testapp/tests/test_stuff.py @@ -17,6 +17,7 @@ from feincms.utils import get_object, shorten_string from feincms.extensions.datepublisher import granular_now + # ------------------------------------------------------------------------ class Empty(object): """ @@ -81,15 +82,14 @@ def test_shorten_string(self): self.assertEqual(string, 'Badger-ger') self.assertEqual(len(string), 10) + # ------------------------------------------------------------------------ class TimezoneTest(TestCase): def test_granular_now_dst_transition(self): # Should not raise an exception d = datetime(2016, 10, 30, 2, 10) tz = pytz.timezone('Europe/Copenhagen') - g = granular_now(d, default_tz=tz) - self.assertEqual(d.hour + 1, g.hour) - self.assertEqual(d.minute, g.minute) + granular_now(d, default_tz=tz) def test_granular_now_rounding(self): d = datetime(2016, 1, 3, 1, 13) From be0c63eabbd8ebf5a36a66bf1f9a963c9aa905a7 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 18 Nov 2016 15:27:03 +0100 Subject: [PATCH 403/654] Fix a few flake8 warnings --- .travis.yml | 4 ++-- docs/releases/1.13.rst | 3 --- feincms/extensions/datepublisher.py | 1 + tests/testapp/models.py | 3 +++ 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f96ca3922..efcf83ded 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ env: - REQ="Django>=1.7,<1.8 django-mptt<0.8" - REQ="Django>=1.8,<1.9 django-mptt<0.8" - REQ="Django>=1.9,<1.10 django-mptt" - - REQ="Django>=1.10,<1.11 django-mptt-nomagic" + - REQ="Django>=1.10,<1.11 django-mptt" matrix: exclude: - python: "3.5" @@ -20,4 +20,4 @@ install: - pip install $REQ Pillow flake8 pytz - python setup.py install # command to run tests, e.g. python setup.py test -script: "cd tests && ./manage.py test testapp && flake8 ." +script: "cd tests && ./manage.py test testapp && cd .. && flake8 ." diff --git a/docs/releases/1.13.rst b/docs/releases/1.13.rst index 3cb2fceca..176e81c67 100644 --- a/docs/releases/1.13.rst +++ b/docs/releases/1.13.rst @@ -9,8 +9,6 @@ Compatibility with Django 1.10 ============================== The biggest feature of Django 1.10 is being compatible with Django 1.10. -Please note that django-mptt_ is at the time of writing not compatible -with Django 1.10 yet. You may want to try django-mptt-nomagic_. Backwards-incompatible changes @@ -50,4 +48,3 @@ FeinCMS 1.13 requires Django 1.7 or better. .. _django-mptt: https://github.com/django-mptt/django-mptt -.. _django-mptt-nomagic: https://pypi.python.org/pypi/django-mptt-nomagic/ diff --git a/feincms/extensions/datepublisher.py b/feincms/extensions/datepublisher.py index e676ccbdd..99d75d3d8 100644 --- a/feincms/extensions/datepublisher.py +++ b/feincms/extensions/datepublisher.py @@ -74,6 +74,7 @@ def granular_now(n=None, default_tz=None): return retval + # ------------------------------------------------------------------------ def datepublisher_response_processor(page, request, response): """ diff --git a/tests/testapp/models.py b/tests/testapp/models.py index a1be16ae0..bf562bc21 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -55,6 +55,7 @@ def get_admin_fields(form, *args, **kwargs): 'custom_field': forms.CharField(), } + Page.create_content_type( ApplicationContent, APPLICATIONS=( @@ -101,6 +102,7 @@ def __str__(self): class ExampleCMSBase(Base): pass + ExampleCMSBase.register_regions( ('region', 'region title'), ('region2', 'region2 title')) @@ -109,6 +111,7 @@ class ExampleCMSBase(Base): class ExampleCMSBase2(Base): pass + ExampleCMSBase2.register_regions( ('region', 'region title'), ('region2', 'region2 title')) From 59d5edd8a30a6a3c487a6728740733dbe7e35538 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 18 Nov 2016 15:29:47 +0100 Subject: [PATCH 404/654] Even more flake8 warnings --- feincms/__init__.py | 1 + feincms/contrib/tagging.py | 1 + feincms/extensions/ct_tracker.py | 2 ++ feincms/module/page/models.py | 3 +++ feincms/templatetags/feincms_page_tags.py | 3 +++ setup.py | 1 + 6 files changed, 11 insertions(+) diff --git a/feincms/__init__.py b/feincms/__init__.py index 748a31680..9d4a6d1bb 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -22,6 +22,7 @@ def __getattr__(self, attr): del self.__class__.__getattr__ return self.__dict__[attr] + settings = LazySettings() diff --git a/feincms/contrib/tagging.py b/feincms/contrib/tagging.py index c2b082f97..4e43af8a6 100644 --- a/feincms/contrib/tagging.py +++ b/feincms/contrib/tagging.py @@ -37,6 +37,7 @@ def taglist_to_string(taglist): # The following is lifted from: # http://code.google.com/p/django-tagging/issues/detail?id=189 + """ TagSelectField diff --git a/feincms/extensions/ct_tracker.py b/feincms/extensions/ct_tracker.py index 77a05fd46..51308e5f8 100644 --- a/feincms/extensions/ct_tracker.py +++ b/feincms/extensions/ct_tracker.py @@ -125,6 +125,8 @@ def class_prepared_handler(sender, **kwargs): # This leads to (lots of) crashes on the server. Better be safe and # kill the translation map when any class_prepared signal is received. _translation_map_cache.clear() + + class_prepared.connect(class_prepared_handler) diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index 933fd5822..20803653c 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -141,6 +141,8 @@ def for_request(self, request, raise404=False, best_match=False, # ------------------------------------------------------------------------ class PageManager(BasePageManager): pass + + PageManager.add_to_active_filters(Q(active=True)) @@ -368,6 +370,7 @@ class Meta: app_label = 'page' # not yet # permissions = (("edit_page", _("Can edit page metadata")),) + Page.register_default_processors() # ------------------------------------------------------------------------ diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py index ae43c573a..b919d2b8f 100644 --- a/feincms/templatetags/feincms_page_tags.py +++ b/feincms/templatetags/feincms_page_tags.py @@ -228,6 +228,7 @@ def what(self, page, args): return links + register.tag( 'feincms_languagelinks', do_simple_assignment_node_with_var_and_args_helper(LanguageLinksNode)) @@ -293,6 +294,7 @@ def what(self, page, args, default=None): return _translate_page_into(page, language, default=default) + register.tag( 'feincms_translatedpage', do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNode)) @@ -305,6 +307,7 @@ def what(self, page, args): page, args, default=getattr(page, 'get_original_translation', page)) + register.tag( 'feincms_translatedpage_or_base', do_simple_assignment_node_with_var_and_args_helper( diff --git a/setup.py b/setup.py index 402c93c61..1e165631a 100755 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ def read(filename): with open(path, encoding='utf-8') as handle: return handle.read() + version = __import__('feincms').__version__ devstatus = 'Development Status :: 5 - Production/Stable' if '.dev' in version: From 6bbb9f4a43bcbe076bd523f676a557c01b98b9dc Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 18 Nov 2016 15:32:09 +0100 Subject: [PATCH 405/654] No nomagic --- tests/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements.txt b/tests/requirements.txt index 162f035fa..2c446f486 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,5 @@ Django -django-mptt-nomagic +django-mptt Pillow coverage pytz From 5ab49544133baa36fe4adf8834e9639a9b63b126 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 18 Nov 2016 15:49:39 +0100 Subject: [PATCH 406/654] FeinCMS v1.13.0 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 9d4a6d1bb..54e23af89 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 12, 1) +VERSION = (1, 13, 0) __version__ = '.'.join(map(str, VERSION)) From 9feb529f1e5ead0701d6f49431d7af943e3bb7a8 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 19 Nov 2016 12:54:33 +0100 Subject: [PATCH 407/654] Remove all uses of allow_tags --- feincms/admin/tree_editor.py | 7 ++----- feincms/extensions/datepublisher.py | 6 +++--- feincms/extensions/translations.py | 4 ++-- feincms/module/medialibrary/modeladmins.py | 9 +++------ feincms/module/page/modeladmins.py | 1 - 5 files changed, 10 insertions(+), 17 deletions(-) diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index e1d288f23..e88ad3941 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -111,7 +111,7 @@ def ajax_editable_boolean_cell(item, attr, text='', override=None): a.insert(0, '
' % (attr, item.pk)) a.append('
') - return ''.join(a) + return mark_safe(''.join(a)) # ------------------------------------------------------------------------ @@ -129,7 +129,6 @@ class MyTreeEditor(TreeEditor): """ def _fn(self, item): return ajax_editable_boolean_cell(item, attr) - _fn.allow_tags = True _fn.short_description = short_description _fn.editable_boolean_field = attr return _fn @@ -280,7 +279,6 @@ def indented_short_title(self, item): # r += '
' return mark_safe(r) indented_short_title.short_description = _('title') - indented_short_title.allow_tags = True def _collect_editable_booleans(self): """ @@ -490,8 +488,7 @@ def _actions_column(self, instance): return [] def actions_column(self, instance): - return ' '.join(self._actions_column(instance)) - actions_column.allow_tags = True + return mark_safe(' '.join(self._actions_column(instance))) actions_column.short_description = _('actions') def delete_selected_tree(self, modeladmin, request, queryset): diff --git a/feincms/extensions/datepublisher.py b/feincms/extensions/datepublisher.py index 99d75d3d8..39c26f6d5 100644 --- a/feincms/extensions/datepublisher.py +++ b/feincms/extensions/datepublisher.py @@ -17,6 +17,7 @@ from django.db.models import Q from django.utils import timezone from django.utils.cache import patch_response_headers +from django.utils.html import mark_safe from django.utils.translation import ugettext_lazy as _ from feincms import extensions @@ -133,11 +134,10 @@ def granular_save(obj, *args, **kwargs): def handle_modeladmin(self, modeladmin): def datepublisher_admin(self, obj): - return '%s – %s' % ( + return mark_safe('%s – %s' % ( format_date(obj.publication_date), format_date(obj.publication_end_date, '∞'), - ) - datepublisher_admin.allow_tags = True + )) datepublisher_admin.short_description = _('visible from - to') modeladmin.__class__.datepublisher_admin = datepublisher_admin diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index bd696320f..a04e21b0b 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -24,6 +24,7 @@ from django.db import models from django.http import HttpResponseRedirect from django.utils import translation +from django.utils.html import mark_safe from django.utils.translation import ugettext_lazy as _ from feincms import extensions, settings @@ -291,9 +292,8 @@ def available_translations_admin(self, page): ) ) - return ' | '.join(links) + return mark_safe(' | '.join(links)) - available_translations_admin.allow_tags = True available_translations_admin.short_description = _('translations') modeladmin.__class__.available_translations_admin =\ available_translations_admin diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 9105505ba..6903d00ae 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -150,7 +150,6 @@ def admin_thumbnail(self, obj): ) return '' admin_thumbnail.short_description = _('Preview') - admin_thumbnail.allow_tags = True def formatted_file_size(self, obj): return filesizeformat(obj.file_size) @@ -177,10 +176,9 @@ def file_type(self, obj): t += " %d×%d" % (d[0], d[1]) except (IOError, TypeError, ValueError) as e: t += " (%s)" % e - return t + return mark_safe(t) file_type.admin_order_field = 'type' file_type.short_description = _('file type') - file_type.allow_tags = True def file_info(self, obj): """ @@ -190,7 +188,7 @@ def file_info(self, obj): the file name later on, this can be used to access the file name from JS, like for example a TinyMCE connector shim. """ - return ( + return mark_safe(( '' ' %s
%s, %s' @@ -201,10 +199,9 @@ def file_info(self, obj): shorten_string(os.path.basename(obj.file.name), max_length=40), self.file_type(obj), self.formatted_file_size(obj), - ) + )) file_info.admin_order_field = 'file' file_info.short_description = _('file info') - file_info.allow_tags = True @staticmethod @csrf_protect diff --git a/feincms/module/page/modeladmins.py b/feincms/module/page/modeladmins.py index 1fda77aee..a01325148 100644 --- a/feincms/module/page/modeladmins.py +++ b/feincms/module/page/modeladmins.py @@ -231,7 +231,6 @@ def is_visible_admin(self, page): page, 'active', override=False, text=_('extensions')) return tree_editor.ajax_editable_boolean_cell(page, 'active') - is_visible_admin.allow_tags = True is_visible_admin.short_description = _('is active') is_visible_admin.editable_boolean_field = 'active' From d7a09c425c15a7baa6922282c19bb754f584c124 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 19 Nov 2016 13:00:49 +0100 Subject: [PATCH 408/654] Add on_delete to foreign keys --- feincms/extensions/translations.py | 1 + feincms/models.py | 3 ++- feincms/module/medialibrary/models.py | 1 + feincms/module/page/extensions/sites.py | 3 ++- feincms/module/page/extensions/symlinks.py | 1 + feincms/module/page/models.py | 1 + feincms/translations.py | 3 ++- tests/testapp/models.py | 3 ++- 8 files changed, 12 insertions(+), 4 deletions(-) diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index a04e21b0b..1c5db1bf3 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -167,6 +167,7 @@ def handle_model(self): 'translation_of', models.ForeignKey( 'self', + on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('translation of'), related_name='translations', limit_choices_to={'language': django_settings.LANGUAGES[0][0]}, diff --git a/feincms/models.py b/feincms/models.py index 8c3a1a743..e52643799 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -517,7 +517,8 @@ def get_queryset(cls, filter_args): 'render': render, 'get_queryset': classmethod(get_queryset), 'Meta': Meta, - 'parent': models.ForeignKey(cls, related_name='%(class)s_set'), + 'parent': models.ForeignKey( + cls, related_name='%(class)s_set', on_delete=models.CASCADE), 'region': models.CharField(max_length=255), 'ordering': models.IntegerField(_('ordering'), default=0), } diff --git a/feincms/module/medialibrary/models.py b/feincms/module/medialibrary/models.py index 2896b56ba..71591a979 100644 --- a/feincms/module/medialibrary/models.py +++ b/feincms/module/medialibrary/models.py @@ -45,6 +45,7 @@ class Category(models.Model): title = models.CharField(_('title'), max_length=200) parent = models.ForeignKey( 'self', blank=True, null=True, + on_delete=models.CASCADE, related_name='children', limit_choices_to={'parent__isnull': True}, verbose_name=_('parent')) diff --git a/feincms/module/page/extensions/sites.py b/feincms/module/page/extensions/sites.py index 536df8710..b0b25d245 100644 --- a/feincms/module/page/extensions/sites.py +++ b/feincms/module/page/extensions/sites.py @@ -18,7 +18,8 @@ def handle_model(self): self.model.add_to_class( 'site', models.ForeignKey( - Site, verbose_name=_('Site'), default=settings.SITE_ID)) + Site, verbose_name=_('Site'), default=settings.SITE_ID, + on_delete=models.CASCADE)) PageManager.add_to_active_filters(current_site, key='current_site') diff --git a/feincms/module/page/extensions/symlinks.py b/feincms/module/page/extensions/symlinks.py index 321f2cc48..c5e4c095b 100644 --- a/feincms/module/page/extensions/symlinks.py +++ b/feincms/module/page/extensions/symlinks.py @@ -18,6 +18,7 @@ def handle_model(self): 'self', blank=True, null=True, + on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_symlinks', verbose_name=_('symlinked page'), help_text=_('All content is inherited from this page if given.'))) diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index 20803653c..fc52af425 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -159,6 +159,7 @@ class BasePage(create_base_model(MPTTModel), ContentModelMixin): help_text=_('This is used to build the URL for this page')) parent = models.ForeignKey( 'self', verbose_name=_('Parent'), blank=True, + on_delete=models.CASCADE, null=True, related_name='children') # Custom list_filter - see admin/filterspecs.py parent.parent_filter = True diff --git a/feincms/translations.py b/feincms/translations.py index 7c9284130..8d860c3fe 100644 --- a/feincms/translations.py +++ b/feincms/translations.py @@ -277,7 +277,8 @@ def Translation(model): """ class Inner(models.Model): - parent = models.ForeignKey(model, related_name='translations') + parent = models.ForeignKey( + model, related_name='translations', on_delete=models.CASCADE) language_code = models.CharField( _('language'), max_length=10, choices=settings.LANGUAGES, default=settings.LANGUAGES[0][0], diff --git a/tests/testapp/models.py b/tests/testapp/models.py index bf562bc21..05328489a 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -88,7 +88,8 @@ class Category(MPTTModel): name = models.CharField(max_length=20) slug = models.SlugField() parent = models.ForeignKey( - 'self', blank=True, null=True, related_name='children') + 'self', blank=True, null=True, related_name='children', + on_delete=models.CASCADE) class Meta: ordering = ['tree_id', 'lft'] From 070ce464cb666dece4bd14abb3d3d096f083eab9 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 19 Nov 2016 13:01:05 +0100 Subject: [PATCH 409/654] Two less warnings --- tests/testapp/tests/test_extensions.py | 4 ++-- tests/testapp/tests/test_page.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 7815d1c27..10c7dc987 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -9,8 +9,8 @@ from django.conf import settings as django_settings from feincms.module.page.models import Page -from feincms.module.extensions.translations import user_has_language_set -from feincms.module.extensions.translations import translation_set_language +from feincms.extensions.translations import user_has_language_set +from feincms.extensions.translations import translation_set_language class TranslationTestCase(TestCase): diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 64db945aa..d2ca81520 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -433,7 +433,7 @@ def test_10_mediafile_and_imagecontent(self): self.assertEqual(force_text(category), 'Category') mediafile = MediaFile.objects.create(file='somefile.jpg') - mediafile.categories = [category] + mediafile.categories.set([category]) page.mediafilecontent_set.create( mediafile=mediafile, region='main', From 8e30d925b369df169cd44ee8d030647e0c1dd0a3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 19 Nov 2016 13:04:37 +0100 Subject: [PATCH 410/654] Shorten a line --- feincms/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feincms/models.py b/feincms/models.py index e52643799..c99c295cb 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -518,7 +518,8 @@ def get_queryset(cls, filter_args): 'get_queryset': classmethod(get_queryset), 'Meta': Meta, 'parent': models.ForeignKey( - cls, related_name='%(class)s_set', on_delete=models.CASCADE), + cls, related_name='%(class)s_set', + on_delete=models.CASCADE), 'region': models.CharField(max_length=255), 'ordering': models.IntegerField(_('ordering'), default=0), } From c14ee0dd2548aaa5791718374bf8fe6275a4d005 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 19 Nov 2016 13:05:29 +0100 Subject: [PATCH 411/654] Revert the m2m set() change --- tests/testapp/tests/test_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index d2ca81520..64db945aa 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -433,7 +433,7 @@ def test_10_mediafile_and_imagecontent(self): self.assertEqual(force_text(category), 'Category') mediafile = MediaFile.objects.create(file='somefile.jpg') - mediafile.categories.set([category]) + mediafile.categories = [category] page.mediafilecontent_set.create( mediafile=mediafile, region='main', From 18d63f3ff3f89ceba2126848479ae413f9d3d776 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 28 Nov 2016 09:55:28 +0100 Subject: [PATCH 412/654] FeinCMS v1.13.1 The less-warnings-release. --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 54e23af89..6c75032b5 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 13, 0) +VERSION = (1, 13, 1) __version__ = '.'.join(map(str, VERSION)) From 94e1dac828db3cab7719a8621a67962ed34689e6 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 7 Feb 2017 14:00:36 +0100 Subject: [PATCH 413/654] The exception is named DoesNotExist on models --- feincms/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/utils/__init__.py b/feincms/utils/__init__.py index f1a697a1e..aef8b207b 100644 --- a/feincms/utils/__init__.py +++ b/feincms/utils/__init__.py @@ -53,7 +53,7 @@ def get_model_instance(app_label, model_name, pk): try: instance = model._default_manager.get(pk=pk) return instance - except model.ObjectDoesNotExist: + except model.DoesNotExist: pass return None From 6c610c8858d5f497366bc9831e8a5f22e409e830 Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Tue, 7 Feb 2017 15:58:46 +0100 Subject: [PATCH 414/654] Update release notes Remove conceptual typo. :) --- docs/releases/1.13.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.13.rst b/docs/releases/1.13.rst index 176e81c67..01e03af4f 100644 --- a/docs/releases/1.13.rst +++ b/docs/releases/1.13.rst @@ -8,7 +8,7 @@ Welcome to FeinCMS 1.13! Compatibility with Django 1.10 ============================== -The biggest feature of Django 1.10 is being compatible with Django 1.10. +The biggest feature of FeinCMS 1.13 is being compatible with Django 1.10. Backwards-incompatible changes From 53a460ed9b69840a67e9db41ca1426322b8439d2 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 12 Apr 2017 11:37:59 +0200 Subject: [PATCH 415/654] Run tests with Django 1.11 too --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index efcf83ded..c9daba12e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - REQ="Django>=1.8,<1.9 django-mptt<0.8" - REQ="Django>=1.9,<1.10 django-mptt" - REQ="Django>=1.10,<1.11 django-mptt" + - REQ="Django>=1.11,<2.0 django-mptt" matrix: exclude: - python: "3.5" From d537ab8793ddef56ad47b2a9c719b244f1d681fd Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 12 Apr 2017 11:42:18 +0200 Subject: [PATCH 416/654] Retroactively update the release notes and mention Django 1.11 --- docs/releases/1.13.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/releases/1.13.rst b/docs/releases/1.13.rst index 01e03af4f..a768743ce 100644 --- a/docs/releases/1.13.rst +++ b/docs/releases/1.13.rst @@ -8,7 +8,8 @@ Welcome to FeinCMS 1.13! Compatibility with Django 1.10 ============================== -The biggest feature of FeinCMS 1.13 is being compatible with Django 1.10. +The biggest feature of FeinCMS 1.13 is being compatible with Django 1.10 +and Django 1.11. Backwards-incompatible changes From 1a02d8853b233585ae675ca78a16b50d3ca36785 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 12 Apr 2017 11:44:38 +0200 Subject: [PATCH 417/654] Add the current development version of Django to .travis.yml --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c9daba12e..b3d89a8a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,14 +11,17 @@ env: - REQ="Django>=1.9,<1.10 django-mptt" - REQ="Django>=1.10,<1.11 django-mptt" - REQ="Django>=1.11,<2.0 django-mptt" + - REQ="https://github.com/django/django/archive/master.zip django-mptt" matrix: exclude: - python: "3.5" env: REQ="Django>=1.7,<1.8 django-mptt<0.8" -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors + - python: "2.7" + env: REQ="https://github.com/django/django/archive/master.zip django-mptt" + allow_failures: + - env: REQ="https://github.com/django/django/archive/master.zip django-mptt" install: - pip install -U pip wheel setuptools - pip install $REQ Pillow flake8 pytz - python setup.py install -# command to run tests, e.g. python setup.py test script: "cd tests && ./manage.py test testapp && cd .. && flake8 ." From b325274ed943a3aea4e72aabc2fcfe9072a6c3c5 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 12 Apr 2017 12:47:14 +0200 Subject: [PATCH 418/654] Work on Django 2.0-compatibility --- feincms/admin/filters.py | 5 ++++- feincms/content/application/models.py | 19 ++++++++++++++----- feincms/module/medialibrary/modeladmins.py | 5 ++++- feincms/module/page/forms.py | 3 ++- feincms/module/page/modeladmins.py | 5 ++++- feincms/module/page/models.py | 2 +- .../templatetags/applicationcontent_tags.py | 5 ++++- feincms/templatetags/feincms_page_tags.py | 5 +++-- feincms/templatetags/feincms_tags.py | 3 ++- tests/testapp/settings.py | 11 ++++++++++- tests/testapp/tests/test_page.py | 11 +++++++++-- tests/testapp/urls.py | 2 +- 12 files changed, 58 insertions(+), 18 deletions(-) diff --git a/feincms/admin/filters.py b/feincms/admin/filters.py index eeb650f0c..cb3187ded 100644 --- a/feincms/admin/filters.py +++ b/feincms/admin/filters.py @@ -76,9 +76,12 @@ def __init__(self, f, request, params, model, model_admin, if DJANGO_VERSION < (1, 8): related_model = f.related.parent_model related_name = f.related.var_name - else: + elif DJANGO_VERSION < (2, 0): related_model = f.rel.to related_name = f.related_query_name() + else: + related_model = f.remote_field.model + related_name = f.related_query_name() self.lookup_choices = sorted( [ diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index 4f3a215a1..9fe3c2e22 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -7,10 +7,6 @@ from django.conf import settings from django.core.cache import cache -from django.core.urlresolvers import ( - NoReverseMatch, reverse, get_script_prefix, set_script_prefix, - Resolver404, resolve, -) from django.db import models from django.http import HttpResponse from django.template.response import TemplateResponse @@ -18,6 +14,16 @@ from django.utils.http import http_date from django.utils.safestring import mark_safe from django.utils.translation import get_language, ugettext_lazy as _ +try: + from django.urls import ( + NoReverseMatch, reverse, get_script_prefix, set_script_prefix, + Resolver404, resolve, + ) +except ImportError: + from django.core.urlresolvers import ( + NoReverseMatch, reverse, get_script_prefix, set_script_prefix, + Resolver404, resolve, + ) from feincms.admin.item_editor import ItemEditorForm from feincms.contrib.fields import JSONField @@ -421,7 +427,10 @@ def app_reverse_cache_key(self, urlconf_path, **kwargs): @classmethod def closest_match(cls, urlconf_path): - page_class = cls.parent.field.rel.to + try: + page_class = cls.parent.field.remote_field.model + except AttributeError: + page_class = cls.parent.field.rel.to contents = cls.objects.filter( parent__in=page_class.objects.active(), diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index 6903d00ae..d52e9fb2c 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -13,13 +13,16 @@ from django.contrib.auth.decorators import permission_required from django.contrib.sites.shortcuts import get_current_site from django.core.files.images import get_image_dimensions -from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.shortcuts import render from django.template.defaultfilters import filesizeformat from django.utils.safestring import mark_safe from django.utils.translation import ungettext, ugettext_lazy as _ from django.views.decorators.csrf import csrf_protect +try: + from django.urls import reverse +except ImportError: + from django.core.urlresolvers import reverse from feincms.extensions import ExtensionModelAdmin from feincms.translations import admin_translationinline, lookup_translations diff --git a/feincms/module/page/forms.py b/feincms/module/page/forms.py index caafb55d2..1e7f3abd8 100644 --- a/feincms/module/page/forms.py +++ b/feincms/module/page/forms.py @@ -115,8 +115,9 @@ def __init__(self, *args, **kwargs): # Note: Using `parent` is not strictly correct, but we can be # sure that `parent` always points to another page instance, # and that's good enough for us. + field = self.page_model._meta.get_field('parent') self.fields['redirect_to'].widget = RedirectToWidget( - self.page_model._meta.get_field('parent').rel, + field.remote_field if hasattr(field, 'remote_field') else field.rel, # noqa modeladmin.admin_site) if 'template_key' in self.fields: diff --git a/feincms/module/page/modeladmins.py b/feincms/module/page/modeladmins.py index a01325148..a913c0db8 100644 --- a/feincms/module/page/modeladmins.py +++ b/feincms/module/page/modeladmins.py @@ -12,10 +12,13 @@ from django.contrib.staticfiles.templatetags.staticfiles import static from django.contrib import admin from django.contrib import messages -from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.utils.functional import curry from django.utils.translation import ugettext_lazy as _ +try: + from django.urls import reverse +except ImportError: + from django.core.urlresolvers import reverse from feincms import ensure_completely_loaded from feincms import settings diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index fc52af425..1274a1187 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -143,7 +143,7 @@ class PageManager(BasePageManager): pass -PageManager.add_to_active_filters(Q(active=True)) +PageManager.add_to_active_filters(Q(active=True), key='is_active') # ------------------------------------------------------------------------ diff --git a/feincms/templatetags/applicationcontent_tags.py b/feincms/templatetags/applicationcontent_tags.py index c698810e3..531e94eda 100644 --- a/feincms/templatetags/applicationcontent_tags.py +++ b/feincms/templatetags/applicationcontent_tags.py @@ -1,10 +1,13 @@ from __future__ import absolute_import, unicode_literals from django import template -from django.core.urlresolvers import NoReverseMatch from django.template import TemplateSyntaxError from django.template.defaulttags import kwarg_re from django.utils.encoding import smart_str +try: + from django.urls import NoReverseMatch +except ImportError: + from django.core.urlresolvers import NoReverseMatch from feincms.apps import ApplicationContent, app_reverse as do_app_reverse from feincms.templatetags.feincms_tags import _render_content diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py index b919d2b8f..c5f617e9e 100644 --- a/feincms/templatetags/feincms_page_tags.py +++ b/feincms/templatetags/feincms_page_tags.py @@ -23,6 +23,7 @@ logger = logging.getLogger('feincms.templatetags.page') register = template.Library() +assignment_tag = getattr(register, 'assignment_tag', register.simple_tag) def _get_page_model(): @@ -38,7 +39,7 @@ def format_exception(e): # ------------------------------------------------------------------------ -@register.assignment_tag(takes_context=True) +@assignment_tag(takes_context=True) def feincms_nav(context, feincms_page, level=1, depth=1, group=None): """ Saves a list of pages into the given context variable. @@ -473,7 +474,7 @@ def siblings_along_path_to(page_list, page2): # ------------------------------------------------------------------------ -@register.assignment_tag(takes_context=True) +@assignment_tag(takes_context=True) def page_is_active(context, page, feincms_page=None, path=None): """ Usage example:: diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index a1820e1d1..9b73e2349 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -14,6 +14,7 @@ register = template.Library() +assignment_tag = getattr(register, 'assignment_tag', register.simple_tag) def _render_content(content, **kwargs): @@ -63,7 +64,7 @@ def feincms_render_content(context, content, request=None): return _render_content(content, request=request, context=context) -@register.assignment_tag +@assignment_tag def feincms_load_singleton(template_key, cls=None): """ {% feincms_load_singleton template_key %} -- return a FeinCMS diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index 6cb030d4a..a89a1c18f 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, unicode_literals +import django import os SITE_ID = 1 @@ -63,7 +64,7 @@ }, }, ] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -71,3 +72,11 @@ 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.locale.LocaleMiddleware' ) + +if django.VERSION < (1, 11): + MIDDLEWARE_CLASSES = MIDDLEWARE + +if django.VERSION >= (2,): + from django.utils import deprecation + # Anything to make mptt.templatetags.mptt_admin importable + deprecation.RemovedInDjango20Warning = deprecation.RemovedInDjango21Warning diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index 64db945aa..e4d342879 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -7,12 +7,12 @@ from datetime import datetime, timedelta import os +import django from django import forms, template from django.conf import settings from django.contrib.auth.models import User, AnonymousUser from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site -from django.core.urlresolvers import reverse from django.db import models from django.http import Http404, HttpResponseBadRequest from django.template import TemplateDoesNotExist @@ -20,6 +20,10 @@ from django.test import TestCase from django.utils import timezone from django.utils.encoding import force_text +try: + from django.urls import reverse +except ImportError: + from django.core.urlresolvers import reverse from mptt.exceptions import InvalidMove @@ -433,7 +437,10 @@ def test_10_mediafile_and_imagecontent(self): self.assertEqual(force_text(category), 'Category') mediafile = MediaFile.objects.create(file='somefile.jpg') - mediafile.categories = [category] + if django.VERSION < (2, 0): + mediafile.categories = [category] + else: + mediafile.categories.set([category]) page.mediafilecontent_set.create( mediafile=mediafile, region='main', diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py index e7d84b18b..f1dbcc4a9 100644 --- a/tests/testapp/urls.py +++ b/tests/testapp/urls.py @@ -16,7 +16,7 @@ admin.autodiscover() urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url( r'^media/(?P.*)$', From bc606fdd9989289d697094ed9f64a6d4a25c7048 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 15 May 2017 10:02:58 +0200 Subject: [PATCH 419/654] Fix the deprecated extensions warnings --- feincms/module/extensions/changedate.py | 5 +++-- feincms/module/extensions/ct_tracker.py | 7 ++++--- feincms/module/extensions/datepublisher.py | 7 ++++--- feincms/module/extensions/featured.py | 7 ++++--- feincms/module/extensions/seo.py | 7 ++++--- feincms/module/extensions/translations.py | 7 ++++--- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/feincms/module/extensions/changedate.py b/feincms/module/extensions/changedate.py index a7bc858c0..dd543dbbc 100644 --- a/feincms/module/extensions/changedate.py +++ b/feincms/module/extensions/changedate.py @@ -6,5 +6,6 @@ from feincms.extensions.changedate import * warnings.warn( - 'Import %s from feincms.extensions.%s' % (__name__, __name__), - DeprecationWarning, stacklevel=2) + 'Import %(name)s from feincms.extensions.%(name)s' % { + 'name': __name__.split('.')[-1], + }, DeprecationWarning, stacklevel=2) diff --git a/feincms/module/extensions/ct_tracker.py b/feincms/module/extensions/ct_tracker.py index 2575e8128..dd543dbbc 100644 --- a/feincms/module/extensions/ct_tracker.py +++ b/feincms/module/extensions/ct_tracker.py @@ -3,8 +3,9 @@ import warnings -from feincms.extensions.ct_tracker import * +from feincms.extensions.changedate import * warnings.warn( - 'Import %s from feincms.extensions.%s' % (__name__, __name__), - DeprecationWarning, stacklevel=2) + 'Import %(name)s from feincms.extensions.%(name)s' % { + 'name': __name__.split('.')[-1], + }, DeprecationWarning, stacklevel=2) diff --git a/feincms/module/extensions/datepublisher.py b/feincms/module/extensions/datepublisher.py index 2ae61540e..dd543dbbc 100644 --- a/feincms/module/extensions/datepublisher.py +++ b/feincms/module/extensions/datepublisher.py @@ -3,8 +3,9 @@ import warnings -from feincms.extensions.datepublisher import * +from feincms.extensions.changedate import * warnings.warn( - 'Import %s from feincms.extensions.%s' % (__name__, __name__), - DeprecationWarning, stacklevel=2) + 'Import %(name)s from feincms.extensions.%(name)s' % { + 'name': __name__.split('.')[-1], + }, DeprecationWarning, stacklevel=2) diff --git a/feincms/module/extensions/featured.py b/feincms/module/extensions/featured.py index f9cf8721e..dd543dbbc 100644 --- a/feincms/module/extensions/featured.py +++ b/feincms/module/extensions/featured.py @@ -3,8 +3,9 @@ import warnings -from feincms.extensions.featured import * +from feincms.extensions.changedate import * warnings.warn( - 'Import %s from feincms.extensions.%s' % (__name__, __name__), - DeprecationWarning, stacklevel=2) + 'Import %(name)s from feincms.extensions.%(name)s' % { + 'name': __name__.split('.')[-1], + }, DeprecationWarning, stacklevel=2) diff --git a/feincms/module/extensions/seo.py b/feincms/module/extensions/seo.py index b3c5f24f2..dd543dbbc 100644 --- a/feincms/module/extensions/seo.py +++ b/feincms/module/extensions/seo.py @@ -3,8 +3,9 @@ import warnings -from feincms.extensions.seo import * +from feincms.extensions.changedate import * warnings.warn( - 'Import %s from feincms.extensions.%s' % (__name__, __name__), - DeprecationWarning, stacklevel=2) + 'Import %(name)s from feincms.extensions.%(name)s' % { + 'name': __name__.split('.')[-1], + }, DeprecationWarning, stacklevel=2) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index 6e4da4f2f..dd543dbbc 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -3,8 +3,9 @@ import warnings -from feincms.extensions.translations import * +from feincms.extensions.changedate import * warnings.warn( - 'Import %s from feincms.extensions.%s' % (__name__, __name__), - DeprecationWarning, stacklevel=2) + 'Import %(name)s from feincms.extensions.%(name)s' % { + 'name': __name__.split('.')[-1], + }, DeprecationWarning, stacklevel=2) From 3c5eee07d957d970652b8d4c9438f3a4057f3b9f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 16 May 2017 18:05:58 +0200 Subject: [PATCH 420/654] Hackfix for queryset_transform on Django 1.11 --- feincms/utils/queryset_transform.py | 31 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py index 3755ab5dd..bc728222d 100644 --- a/feincms/utils/queryset_transform.py +++ b/feincms/utils/queryset_transform.py @@ -83,6 +83,7 @@ def lookup_tags(item_qs): from __future__ import absolute_import, unicode_literals +import django from django.db import models @@ -102,20 +103,28 @@ def transform(self, *fn): c._transform_fns.extend(fn) return c - def iterator(self): - result_iter = super(TransformQuerySet, self).iterator() + if django.VERSION < (1, 11): + def iterator(self): + result_iter = super(TransformQuerySet, self).iterator() - if not self._transform_fns: - return result_iter + if not self._transform_fns: + return result_iter - if getattr(self, '_iterable_class', None) != self._orig_iterable_class: - # Do not process the result of values() and values_list() - return result_iter + if getattr(self, '_iterable_class', None) != self._orig_iterable_class: + # Do not process the result of values() and values_list() + return result_iter - results = list(result_iter) - for fn in self._transform_fns: - fn(results) - return iter(results) + results = list(result_iter) + for fn in self._transform_fns: + fn(results) + return iter(results) + + else: + def _fetch_all(self): + super()._fetch_all() + if getattr(self, '_iterable_class', None) == self._orig_iterable_class: + for fn in self._transform_fns: + fn(self._result_cache) if hasattr(models.Manager, 'from_queryset'): From d2e565e49ffa43ac3a542f9caead36e10634ef13 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 16 May 2017 18:10:49 +0200 Subject: [PATCH 421/654] Avoid a few deprecation warnings --- docs/deprecation.rst | 2 +- feincms/module/page/models.py | 9 ++++++--- feincms/templatetags/feincms_page_tags.py | 6 +++++- feincms/templatetags/feincms_tags.py | 6 +++++- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/deprecation.rst b/docs/deprecation.rst index 9bc7fb74e..12c6d8aaa 100644 --- a/docs/deprecation.rst +++ b/docs/deprecation.rst @@ -125,4 +125,4 @@ No deprecations. 1.12 ==== -* TODO update this \ No newline at end of file +* TODO update this diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index 1274a1187..1f6f5262f 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -10,6 +10,10 @@ from django.http import Http404 from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ +try: + from django.urls import reverse +except ImportError: + from django.core.urlresolvers import reverse from mptt.models import MPTTModel, TreeManager @@ -285,7 +289,6 @@ def delete(self, *args, **kwargs): super(BasePage, self).delete(*args, **kwargs) delete.alters_data = True - @models.permalink def get_absolute_url(self): """ Return the absolute URL of this page. @@ -293,8 +296,8 @@ def get_absolute_url(self): # result url never begins or ends with a slash url = self._cached_url.strip('/') if url: - return ('feincms_handler', (url,), {}) - return ('feincms_home', (), {}) + return reverse('feincms_handler', args=(url,)) + return reverse('feincms_home') def get_navigation_url(self): """ diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py index c5f617e9e..2ea6cd7c1 100644 --- a/feincms/templatetags/feincms_page_tags.py +++ b/feincms/templatetags/feincms_page_tags.py @@ -8,6 +8,7 @@ import sys import traceback +import django from django import template from django.apps import apps from django.conf import settings @@ -23,7 +24,10 @@ logger = logging.getLogger('feincms.templatetags.page') register = template.Library() -assignment_tag = getattr(register, 'assignment_tag', register.simple_tag) +assignment_tag = ( + register.simple_tag if django.VERSION >= (1, 9) + else register.assignment_tag +) def _get_page_model(): diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index 9b73e2349..3b71011bf 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -6,6 +6,7 @@ import logging +import django from django import template from django.conf import settings from django.utils.safestring import mark_safe @@ -14,7 +15,10 @@ register = template.Library() -assignment_tag = getattr(register, 'assignment_tag', register.simple_tag) +assignment_tag = ( + register.simple_tag if django.VERSION >= (1, 9) + else register.assignment_tag +) def _render_content(content, **kwargs): From 3e952abac723ae8a219cfb3f322f9506d912fff6 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 16 May 2017 18:11:54 +0200 Subject: [PATCH 422/654] Oops --- feincms/utils/queryset_transform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py index bc728222d..7b2d0843e 100644 --- a/feincms/utils/queryset_transform.py +++ b/feincms/utils/queryset_transform.py @@ -110,7 +110,7 @@ def iterator(self): if not self._transform_fns: return result_iter - if getattr(self, '_iterable_class', None) != self._orig_iterable_class: + if getattr(self, '_iterable_class', None) != self._orig_iterable_class: # noqa # Do not process the result of values() and values_list() return result_iter @@ -122,7 +122,7 @@ def iterator(self): else: def _fetch_all(self): super()._fetch_all() - if getattr(self, '_iterable_class', None) == self._orig_iterable_class: + if getattr(self, '_iterable_class', None) == self._orig_iterable_class: # noqa for fn in self._transform_fns: fn(self._result_cache) From a66f452f2aa10cf7f6e5c24426124bc23b6e4e28 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 16 May 2017 18:20:46 +0200 Subject: [PATCH 423/654] Python 2 strikes again --- feincms/utils/queryset_transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py index 7b2d0843e..e7110a7d1 100644 --- a/feincms/utils/queryset_transform.py +++ b/feincms/utils/queryset_transform.py @@ -121,7 +121,7 @@ def iterator(self): else: def _fetch_all(self): - super()._fetch_all() + super(TransformQuerySet, self)._fetch_all() if getattr(self, '_iterable_class', None) == self._orig_iterable_class: # noqa for fn in self._transform_fns: fn(self._result_cache) From f8f867b7e7e4f58982cf4e0eb105aad0f7fe129c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 16 May 2017 18:34:03 +0200 Subject: [PATCH 424/654] FeinCMS v1.13.2 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 6c75032b5..c9e218894 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 13, 1) +VERSION = (1, 13, 2) __version__ = '.'.join(map(str, VERSION)) From 1a37943ff37b115f93d4bd029a48c37b9d7ab13c Mon Sep 17 00:00:00 2001 From: Henning Hraban Ramm Date: Wed, 24 May 2017 13:56:01 +0200 Subject: [PATCH 425/654] Fix import This module is importing from the wrong source --- feincms/module/extensions/translations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index dd543dbbc..7d8842186 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -3,7 +3,7 @@ import warnings -from feincms.extensions.changedate import * +from feincms.extensions.translations import * warnings.warn( 'Import %(name)s from feincms.extensions.%(name)s' % { From efbe6799790e8b544ceb14ff750347d6d6f09241 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 24 May 2017 15:13:03 +0200 Subject: [PATCH 426/654] Also fix other fallback imports --- feincms/module/extensions/ct_tracker.py | 2 +- feincms/module/extensions/datepublisher.py | 2 +- feincms/module/extensions/featured.py | 2 +- feincms/module/extensions/seo.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/feincms/module/extensions/ct_tracker.py b/feincms/module/extensions/ct_tracker.py index dd543dbbc..7b94fd55f 100644 --- a/feincms/module/extensions/ct_tracker.py +++ b/feincms/module/extensions/ct_tracker.py @@ -3,7 +3,7 @@ import warnings -from feincms.extensions.changedate import * +from feincms.extensions.ct_tracker import * warnings.warn( 'Import %(name)s from feincms.extensions.%(name)s' % { diff --git a/feincms/module/extensions/datepublisher.py b/feincms/module/extensions/datepublisher.py index dd543dbbc..44fed5edb 100644 --- a/feincms/module/extensions/datepublisher.py +++ b/feincms/module/extensions/datepublisher.py @@ -3,7 +3,7 @@ import warnings -from feincms.extensions.changedate import * +from feincms.extensions.datepublisher import * warnings.warn( 'Import %(name)s from feincms.extensions.%(name)s' % { diff --git a/feincms/module/extensions/featured.py b/feincms/module/extensions/featured.py index dd543dbbc..05b59a87a 100644 --- a/feincms/module/extensions/featured.py +++ b/feincms/module/extensions/featured.py @@ -3,7 +3,7 @@ import warnings -from feincms.extensions.changedate import * +from feincms.extensions.featured import * warnings.warn( 'Import %(name)s from feincms.extensions.%(name)s' % { diff --git a/feincms/module/extensions/seo.py b/feincms/module/extensions/seo.py index dd543dbbc..8dc6add93 100644 --- a/feincms/module/extensions/seo.py +++ b/feincms/module/extensions/seo.py @@ -3,7 +3,7 @@ import warnings -from feincms.extensions.changedate import * +from feincms.extensions.seo import * warnings.warn( 'Import %(name)s from feincms.extensions.%(name)s' % { From 24eebd67e494ffa2d56986851612979c19f81b1f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 24 May 2017 15:39:13 +0200 Subject: [PATCH 427/654] FeinCMS v1.13.3 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index c9e218894..5bd4677ea 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 13, 2) +VERSION = (1, 13, 3) __version__ = '.'.join(map(str, VERSION)) From 21afade3cf7a36c753c111b68854d58ccd65deec Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 8 Jun 2017 14:52:58 +0200 Subject: [PATCH 428/654] Generate translations' change URLs the correct way --- feincms/templates/admin/feincms/item_editor.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feincms/templates/admin/feincms/item_editor.html b/feincms/templates/admin/feincms/item_editor.html index cb01e8f1f..a83a4bc9d 100644 --- a/feincms/templates/admin/feincms/item_editor.html +++ b/feincms/templates/admin/feincms/item_editor.html @@ -1,5 +1,5 @@ {% extends "admin/change_form.html" %} -{% load i18n admin_modify staticfiles %} +{% load i18n admin_modify admin_urls staticfiles %} {% block extrahead %}{{ block.super }} {% block feincms_jquery_ui %} @@ -31,7 +31,7 @@
{% trans "available translations" %}: {% endif %} - {{ translation.language|upper }}{% if not forloop.last %},{% endif %} + {{ translation.language|upper }}{% if not forloop.last %},{% endif %} {% if forloop.last %}
diff --git a/feincms/templates/admin/feincms/content_inline_dj20.html b/feincms/templates/admin/feincms/content_inline_dj20.html new file mode 100644 index 000000000..9dd72347c --- /dev/null +++ b/feincms/templates/admin/feincms/content_inline_dj20.html @@ -0,0 +1,31 @@ +{% load i18n admin_urls admin_static feincms_admin_tags %} +
+

{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

+{{ inline_admin_formset.formset.management_form }} +{{ inline_admin_formset.formset.non_form_errors }} + +{% for inline_admin_form in inline_admin_formset %}
+

{{ inline_admin_formset.opts.verbose_name|capfirst }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} {% trans "Change" %}{% endif %} +{% else %}#{{ forloop.counter }}{% endif %} + {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %} +

+ {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} + {% for fieldset in inline_admin_form %} + {% post_process_fieldsets fieldset %} + {% include "admin/includes/fieldset.html" %} + {% endfor %} + {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %} + {{ inline_admin_form.fk_field.field }} +
{% endfor %} +
+ + From 78ec29ad50740ef14f265663c7090a960f7c2e4f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 27 Aug 2018 15:20:41 +0200 Subject: [PATCH 474/654] Mention the fix in the CHANGELOG --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cd9db6621..fbdf903ec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,8 @@ Change log ``FEINCMS_THUMBNAIL_CACHE_TIMEOUT`` instead of the hardcoded value of seven days. - Reverted the deprecation of navigation extension autodiscovery. +- Fixed the item editor JavaScript and HTML to work with Django 2.1's + updated inlines. `v1.14.0`_ (2018-08-16) From 0ef94d6100b4c0a2782401467940f5d4671a0497 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 27 Aug 2018 15:21:45 +0200 Subject: [PATCH 475/654] Django 2.2's admin app starts checking INSTALLED_APPS it seems --- tests/testapp/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index a04015c44..560875262 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -16,6 +16,7 @@ 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', + 'django.contrib.messages', 'django.contrib.sessions', 'django.contrib.sitemaps', 'django.contrib.sites', From d8caa2f9c7a9ffb79d3bbfc88e27e26ab0b31cad Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 27 Aug 2018 15:28:35 +0200 Subject: [PATCH 476/654] FeinCMS v1.14.2 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index 78d50b052..b8bee14f7 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 14, 1) +VERSION = (1, 14, 2) __version__ = '.'.join(map(str, VERSION)) From e915e2c86905482ce13e7fb835f9c765e64164b1 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 23 Oct 2018 10:24:33 +0200 Subject: [PATCH 477/654] Easy tox test runner --- .gitignore | 4 ++++ tests/cov.sh | 3 --- tox.ini | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) delete mode 100755 tests/cov.sh create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore index 45f25c901..5dd000736 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,7 @@ tests/test.zip /tests/.coverage /tests/htmlcov venv +.tox +.coverage +htmlcov +test.zip diff --git a/tests/cov.sh b/tests/cov.sh deleted file mode 100755 index c577a1f65..000000000 --- a/tests/cov.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -venv/bin/coverage run --branch --include="*feincms/feincms*" ./manage.py test testapp -venv/bin/coverage html diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..3f96dd417 --- /dev/null +++ b/tox.ini @@ -0,0 +1,39 @@ +[testenv] +basepython = python3 + +[testenv:style] +deps = + flake8 +changedir = {toxinidir} +commands = + flake8 . +skip_install = true + +# [testenv:docs] +# deps = +# Sphinx +# sphinx-rtd-theme +# Django +# django-ckeditor +# django-content-editor +# django-tree-queries +# django-imagefield +# django-versatileimagefield +# html-sanitizer +# requests +# changedir = docs +# commands = make html +# skip_install = true +# whitelist_externals = make + +[testenv:tests] +deps = + Django + django-mptt + Pillow + coverage +changedir = {toxinidir} +skip_install = true +commands = + coverage run tests/manage.py test -v 2 {posargs:testapp} + coverage html From accd7a8246410a797e11ce3f1d0d964ec48cfcb1 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 23 Oct 2018 10:27:41 +0200 Subject: [PATCH 478/654] Evaluate callables passed to only_language --- CHANGELOG.rst | 2 ++ feincms/translations.py | 4 +++- tests/testapp/tests/test_page.py | 6 ++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fbdf903ec..57ecdc338 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,8 @@ Change log - Reverted the deprecation of navigation extension autodiscovery. - Fixed the item editor JavaScript and HTML to work with Django 2.1's updated inlines. +- Fixed ``TranslatedObjectManager.only_language`` to evaluate callables + before filtering. `v1.14.0`_ (2018-08-16) diff --git a/feincms/translations.py b/feincms/translations.py index 8d860c3fe..eaab23da9 100644 --- a/feincms/translations.py +++ b/feincms/translations.py @@ -169,7 +169,9 @@ def only_language(self, language=short_language_code): Uses the currently active language by default. """ - return self.filter(translations__language_code=language) + return self.filter(translations__language_code=( + language() if callable(language) else language + )) @python_2_unicode_compatible diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index e4d342879..a9c48e0e8 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -468,8 +468,10 @@ def test_10_mediafile_and_imagecontent(self): self.assertEqual(MediaFile.objects.only_language('en').count(), 0) self.assertEqual( MediaFile.objects.only_language( - '%s-ha' % short_language_code()).count(), - 1) + lambda: '%s-ha' % short_language_code() + ).count(), + 1, + ) self.assertTrue( '%s-ha' % short_language_code() in mf.available_translations) From f270fb1707cf363e6c71171868cb791441d5e9b6 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 23 Oct 2018 10:29:22 +0200 Subject: [PATCH 479/654] FeinCMS v1.14.3 --- feincms/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/__init__.py b/feincms/__init__.py index b8bee14f7..64452ceb7 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 14, 2) +VERSION = (1, 14, 3) __version__ = '.'.join(map(str, VERSION)) From 81f690f608311ddabe0baa703b6237db936c25b3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 21 Dec 2018 10:11:15 +0100 Subject: [PATCH 480/654] This is still the official location --- feincms/views/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feincms/views/decorators.py b/feincms/views/decorators.py index 365761807..2fab30127 100644 --- a/feincms/views/decorators.py +++ b/feincms/views/decorators.py @@ -6,5 +6,5 @@ from feincms.apps import * warnings.warn( - 'Import ApplicationContent and friends from feincms.apps.', + 'Import ApplicationContent and friends from feincms.content.application.models', DeprecationWarning, stacklevel=2) From 458950f9503a1ac38a441f32bc65552d26dd110f Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 21 Dec 2018 10:23:48 +0100 Subject: [PATCH 481/654] Allow rendering plugin templates in the context of their parent template --- CHANGELOG.rst | 4 ++++ feincms/models.py | 2 +- feincms/templatetags/feincms_tags.py | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 57ecdc338..0bca657ab 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,10 @@ Change log updated inlines. - Fixed ``TranslatedObjectManager.only_language`` to evaluate callables before filtering. +- Changed the ``render`` protocol of content types to allow returning a + tuple of ``(ct_template, ct_context)`` which works the same way as + `feincms3's template renderers + `__. `v1.14.0`_ (2018-08-16) diff --git a/feincms/models.py b/feincms/models.py index c99c295cb..0e3d49b2c 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -2,7 +2,7 @@ This is the core of FeinCMS All models defined here are abstract, which means no tables are created in -the feincms\_ namespace. +the feincms namespace. """ from __future__ import absolute_import, unicode_literals diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index 3b71011bf..a0de9da09 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -9,6 +9,7 @@ import django from django import template from django.conf import settings +from django.template.engine import Engine from django.utils.safestring import mark_safe from feincms.utils import get_singleton, get_singleton_url @@ -37,6 +38,25 @@ def _render_content(content, **kwargs): r = content.render(**kwargs) + if isinstance(r, (list, tuple)): + # Modeled after feincms3's TemplatePluginRenderer + context = kwargs["context"] + plugin_template, plugin_context = r + + if not hasattr(plugin_template, "render"): # Quacks like a template? + try: + engine = context.template.engine + except AttributeError: + engine = Engine.get_default() + + if isinstance(plugin_template, (list, tuple)): + plugin_template = engine.select_template(plugin_template) + else: + plugin_template = engine.get_template(plugin_template) + + with context.push(plugin_context): + return plugin_template.render(context) + if request is not None: level = getattr(request, 'feincms_render_level', 1) setattr(request, 'feincms_render_level', max(level - 1, 0)) From 15a3ac899da948041436dd456d586008197c12a7 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 21 Dec 2018 10:28:22 +0100 Subject: [PATCH 482/654] Late import for Django 1.7 compatibility --- feincms/templatetags/feincms_tags.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index a0de9da09..1450d7ace 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -9,7 +9,6 @@ import django from django import template from django.conf import settings -from django.template.engine import Engine from django.utils.safestring import mark_safe from feincms.utils import get_singleton, get_singleton_url @@ -47,6 +46,11 @@ def _render_content(content, **kwargs): try: engine = context.template.engine except AttributeError: + # This fails hard in Django 1.7 (ImportError). So what. This + # just means that this particular feature isn't available + # there. + from django.template.engine import Engine + engine = Engine.get_default() if isinstance(plugin_template, (list, tuple)): From 435e462d85d1f6ba3f598e97b2110b9f29a17d83 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 21 Dec 2018 10:31:35 +0100 Subject: [PATCH 483/654] Add migrations so that test models are created _after_ e.g. sites.Site --- setup.cfg | 2 +- tests/testapp/migrations/0001_initial.py | 85 ++++++++++++++++++++++++ tests/testapp/migrations/__init__.py | 0 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 tests/testapp/migrations/0001_initial.py create mode 100644 tests/testapp/migrations/__init__.py diff --git a/setup.cfg b/setup.cfg index e0f9c91ef..cc4bd75fa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] -exclude=venv,.tox,build,docs +exclude=venv,.tox,build,docs,migrations [wheel] universal = 1 diff --git a/tests/testapp/migrations/0001_initial.py b/tests/testapp/migrations/0001_initial.py new file mode 100644 index 000000000..a851c06a8 --- /dev/null +++ b/tests/testapp/migrations/0001_initial.py @@ -0,0 +1,85 @@ +# Generated by Django 2.1.4 on 2018-12-21 09:29 + +from django.db import migrations, models +import django.db.models.deletion +import feincms.extensions.base + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=20)), + ('slug', models.SlugField()), + ('lft', models.PositiveIntegerField(db_index=True, editable=False)), + ('rght', models.PositiveIntegerField(db_index=True, editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(db_index=True, editable=False)), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='testapp.Category')), + ], + options={ + 'verbose_name': 'category', + 'verbose_name_plural': 'categories', + 'ordering': ['tree_id', 'lft'], + }, + ), + migrations.CreateModel( + name='CustomContentType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('region', models.CharField(max_length=255)), + ('ordering', models.IntegerField(default=0, verbose_name='ordering')), + ], + options={ + 'verbose_name': 'custom content type', + 'verbose_name_plural': 'custom content types', + 'db_table': 'testapp_mymodel_customcontenttype', + 'ordering': ['ordering'], + 'permissions': [], + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ExampleCMSBase', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, feincms.extensions.base.ExtensionsMixin), + ), + migrations.CreateModel( + name='ExampleCMSBase2', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, feincms.extensions.base.ExtensionsMixin), + ), + migrations.CreateModel( + name='MyModel', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + options={ + 'abstract': False, + }, + bases=(models.Model, feincms.extensions.base.ExtensionsMixin), + ), + migrations.AddField( + model_name='customcontenttype', + name='parent', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customcontenttype_set', to='testapp.MyModel'), + ), + ] diff --git a/tests/testapp/migrations/__init__.py b/tests/testapp/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb From 93cac884ed88cfc618df5cbe8937b8868275395a Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 21 Dec 2018 10:54:47 +0100 Subject: [PATCH 484/654] FeinCMS v1.15.0 --- CHANGELOG.rst | 7 ++++++- feincms/__init__.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0bca657ab..ebcfd15bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,10 @@ Change log `Next version`_ ~~~~~~~~~~~~~~~ + +`v1.15.0`_ (2018-12-21) +~~~~~~~~~~~~~~~~~~~~~~~ + - Actually made use of the timeout specified as ``FEINCMS_THUMBNAIL_CACHE_TIMEOUT`` instead of the hardcoded value of seven days. @@ -52,4 +56,5 @@ Change log .. _v1.14.0: https://github.com/feincms/feincms/compare/v1.13.0...v1.14.0 -.. _Next version: https://github.com/feincms/feincms/compare/v1.14.0...master +.. _v1.15.0: https://github.com/feincms/feincms/compare/v1.14.0...v1.15.0 +.. _Next version: https://github.com/feincms/feincms/compare/v1.15.0...master diff --git a/feincms/__init__.py b/feincms/__init__.py index 64452ceb7..c78c98e30 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 14, 3) +VERSION = (1, 15, 0) __version__ = '.'.join(map(str, VERSION)) From 93662596c0d4ba7c8f278d602283bdf848b1f3b1 Mon Sep 17 00:00:00 2001 From: "Martin J. Laubach" Date: Wed, 16 Jan 2019 11:16:45 +0100 Subject: [PATCH 485/654] Make the rebuild_mptt management command work with Django 1.11+ --- feincms/management/commands/rebuild_mptt.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/feincms/management/commands/rebuild_mptt.py b/feincms/management/commands/rebuild_mptt.py index 5e2b2c2cb..b99291d02 100644 --- a/feincms/management/commands/rebuild_mptt.py +++ b/feincms/management/commands/rebuild_mptt.py @@ -10,16 +10,22 @@ from __future__ import absolute_import, unicode_literals -from django.core.management.base import NoArgsCommand +try: + from django.core.management.base import NoArgsCommand as BaseCommand +except ImportError: + from django.core.management.base import BaseCommand from feincms.module.page.models import Page -class Command(NoArgsCommand): +class Command(BaseCommand): help = ( "Run this manually to rebuild your mptt pointers. Only use in" " emergencies.") def handle_noargs(self, **options): + self.handle(**options) + + def handle(self, **options): self.stdout.write("Rebuilding MPTT pointers for Page") Page._tree_manager.rebuild() From a3d3d4b49b9a46c9eb093f79b65a113aa8e4f9ae Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 1 Feb 2019 18:19:57 +0100 Subject: [PATCH 486/654] Reformat everything using black --- CHANGELOG.rst | 2 + feincms/__init__.py | 19 +- feincms/_internal.py | 28 +- feincms/admin/__init__.py | 10 +- feincms/admin/filters.py | 66 +- feincms/admin/item_editor.py | 139 +- feincms/admin/tree_editor.py | 295 ++-- feincms/content/application/models.py | 219 +-- feincms/content/contactform/__init__.py | 6 +- feincms/content/contactform/models.py | 52 +- feincms/content/file/models.py | 21 +- feincms/content/filer/models.py | 61 +- feincms/content/image/models.py | 51 +- feincms/content/medialibrary/models.py | 6 +- feincms/content/raw/models.py | 6 +- feincms/content/richtext/models.py | 21 +- feincms/content/section/models.py | 75 +- feincms/content/template/models.py | 20 +- feincms/content/video/models.py | 53 +- feincms/context_processors.py | 4 +- feincms/contrib/fields.py | 11 +- feincms/contrib/preview/urls.py | 3 +- feincms/contrib/preview/views.py | 8 +- feincms/contrib/richtext.py | 16 +- feincms/contrib/tagging.py | 59 +- feincms/default_settings.py | 98 +- feincms/extensions/__init__.py | 13 +- feincms/extensions/base.py | 45 +- feincms/extensions/changedate.py | 23 +- feincms/extensions/ct_tracker.py | 54 +- feincms/extensions/datepublisher.py | 75 +- feincms/extensions/featured.py | 13 +- feincms/extensions/seo.py | 43 +- feincms/extensions/translations.py | 125 +- .../commands/medialibrary_orphans.py | 4 +- .../commands/medialibrary_to_filer.py | 28 +- feincms/management/commands/rebuild_mptt.py | 6 +- feincms/models.py | 317 ++-- feincms/module/extensions/changedate.py | 8 +- feincms/module/extensions/ct_tracker.py | 8 +- feincms/module/extensions/datepublisher.py | 8 +- feincms/module/extensions/featured.py | 8 +- feincms/module/extensions/seo.py | 8 +- feincms/module/extensions/translations.py | 8 +- feincms/module/medialibrary/__init__.py | 2 +- feincms/module/medialibrary/contents.py | 33 +- feincms/module/medialibrary/fields.py | 50 +- feincms/module/medialibrary/forms.py | 43 +- feincms/module/medialibrary/modeladmins.py | 154 +- feincms/module/medialibrary/models.py | 154 +- feincms/module/medialibrary/thumbnail.py | 9 +- feincms/module/medialibrary/zip.py | 99 +- feincms/module/mixins.py | 38 +- feincms/module/page/admin.py | 2 +- feincms/module/page/extensions/excerpt.py | 17 +- feincms/module/page/extensions/navigation.py | 56 +- .../page/extensions/navigationgroups.py | 21 +- .../module/page/extensions/relatedpages.py | 24 +- feincms/module/page/extensions/sites.py | 18 +- feincms/module/page/extensions/symlinks.py | 29 +- feincms/module/page/extensions/titles.py | 48 +- feincms/module/page/forms.py | 155 +- feincms/module/page/modeladmins.py | 173 +- feincms/module/page/models.py | 164 +- feincms/module/page/processors.py | 54 +- feincms/module/page/sitemap.py | 35 +- feincms/shortcuts.py | 2 +- .../templatetags/applicationcontent_tags.py | 41 +- feincms/templatetags/feincms_admin_tags.py | 35 +- feincms/templatetags/feincms_page_tags.py | 142 +- feincms/templatetags/feincms_tags.py | 32 +- feincms/templatetags/feincms_thumbnail.py | 108 +- feincms/templatetags/fragment_tags.py | 25 +- feincms/translations.py | 96 +- feincms/urls.py | 4 +- feincms/utils/__init__.py | 51 +- feincms/utils/queryset_transform.py | 15 +- feincms/utils/templatetags.py | 31 +- feincms/views/__init__.py | 27 +- feincms/views/decorators.py | 6 +- setup.cfg | 5 +- setup.py | 70 +- tests/manage.py | 3 +- tests/testapp/applicationcontent_urls.py | 45 +- tests/testapp/context_processors.py | 2 +- tests/testapp/migrations/0001_initial.py | 130 +- tests/testapp/models.py | 106 +- tests/testapp/navigation_extensions.py | 9 +- tests/testapp/settings.py | 99 +- tests/testapp/tests/test_cms.py | 55 +- tests/testapp/tests/test_extensions.py | 70 +- tests/testapp/tests/test_page.py | 1480 ++++++++--------- tests/testapp/tests/test_stuff.py | 42 +- tests/testapp/tests/utils.py | 2 + tests/testapp/urls.py | 21 +- tox.ini | 2 + 96 files changed, 3296 insertions(+), 2981 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ebcfd15bc..5a54bc50d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Change log `Next version`_ ~~~~~~~~~~~~~~~ +- Reformatted everything using black. + `v1.15.0`_ (2018-12-21) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/feincms/__init__.py b/feincms/__init__.py index c78c98e30..308b8f1cd 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,7 +1,7 @@ from __future__ import absolute_import, unicode_literals VERSION = (1, 15, 0) -__version__ = '.'.join(map(str, VERSION)) +__version__ = ".".join(map(str, VERSION)) class LazySettings(object): @@ -10,7 +10,7 @@ def _load_settings(self): from django.conf import settings as django_settings for key in dir(default_settings): - if not key.startswith('FEINCMS_'): + if not key.startswith("FEINCMS_"): continue value = getattr(default_settings, key) @@ -46,6 +46,7 @@ def ensure_completely_loaded(force=False): return True from django.apps import apps + if not apps.ready: return @@ -61,13 +62,17 @@ def ensure_completely_loaded(force=False): import django from distutils.version import LooseVersion - if LooseVersion(django.get_version()) < LooseVersion('1.8'): + if LooseVersion(django.get_version()) < LooseVersion("1.8"): for model in apps.get_models(): for cache_name in ( - '_field_cache', '_field_name_cache', '_m2m_cache', - '_related_objects_cache', '_related_many_to_many_cache', - '_name_map'): + "_field_cache", + "_field_name_cache", + "_m2m_cache", + "_related_objects_cache", + "_related_many_to_many_cache", + "_name_map", + ): try: delattr(model._meta, cache_name) except AttributeError: @@ -85,7 +90,7 @@ def ensure_completely_loaded(force=False): # get_models cache again here. If we don't do this, Django 1.5 chokes # on a model validation error (Django 1.4 doesn't exhibit this # problem). See Issue #323 on github. - if hasattr(apps, 'cache'): + if hasattr(apps, "cache"): apps.cache.get_models.cache_clear() if apps.ready: diff --git a/feincms/_internal.py b/feincms/_internal.py index 134046da1..44a669748 100644 --- a/feincms/_internal.py +++ b/feincms/_internal.py @@ -11,9 +11,7 @@ from django.template.loader import render_to_string -__all__ = ( - 'monkeypatch_method', 'monkeypatch_property', -) +__all__ = ("monkeypatch_method", "monkeypatch_property") def monkeypatch_method(cls): @@ -28,6 +26,7 @@ def (self, [...]): def decorator(func): setattr(cls, func.__name__, func) return func + return decorator @@ -43,24 +42,23 @@ def (self, [...]): def decorator(func): setattr(cls, func.__name__, property(func)) return func + return decorator -if LooseVersion(get_version()) < LooseVersion('1.10'): +if LooseVersion(get_version()) < LooseVersion("1.10"): + def ct_render_to_string(template, ctx, **kwargs): from django.template import RequestContext - context_instance = kwargs.get('context') - if context_instance is None and kwargs.get('request'): - context_instance = RequestContext(kwargs['request']) + context_instance = kwargs.get("context") + if context_instance is None and kwargs.get("request"): + context_instance = RequestContext(kwargs["request"]) + + return render_to_string(template, ctx, context_instance=context_instance) + - return render_to_string( - template, - ctx, - context_instance=context_instance) else: + def ct_render_to_string(template, ctx, **kwargs): - return render_to_string( - template, - ctx, - request=kwargs.get('request')) + return render_to_string(template, ctx, request=kwargs.get("request")) diff --git a/feincms/admin/__init__.py b/feincms/admin/__init__.py index 0a2edfe1f..9008a9982 100644 --- a/feincms/admin/__init__.py +++ b/feincms/admin/__init__.py @@ -5,10 +5,12 @@ FieldListFilter.register( - lambda f: getattr(f, 'parent_filter', False), + lambda f: getattr(f, "parent_filter", False), ParentFieldListFilter, - take_priority=True) + take_priority=True, +) FieldListFilter.register( - lambda f: getattr(f, 'category_filter', False), + lambda f: getattr(f, "category_filter", False), CategoryFieldListFilter, - take_priority=True) + take_priority=True, +) diff --git a/feincms/admin/filters.py b/feincms/admin/filters.py index cb3187ded..75a1a73c2 100644 --- a/feincms/admin/filters.py +++ b/feincms/admin/filters.py @@ -26,38 +26,44 @@ class ParentFieldListFilter(ChoicesFieldListFilter): my_model_field.page_parent_filter = True """ - def __init__(self, f, request, params, model, model_admin, - field_path=None): + def __init__(self, f, request, params, model, model_admin, field_path=None): super(ParentFieldListFilter, self).__init__( - f, request, params, model, model_admin, field_path) + f, request, params, model, model_admin, field_path + ) - parent_ids = model.objects.exclude(parent=None).values_list( - "parent__id", flat=True).order_by("parent__id").distinct() + parent_ids = ( + model.objects.exclude(parent=None) + .values_list("parent__id", flat=True) + .order_by("parent__id") + .distinct() + ) parents = model.objects.filter(pk__in=parent_ids).values_list( - "pk", "title", "level") - self.lookup_choices = [( - pk, - "%s%s" % ( - "  " * level, - shorten_string(title, max_length=25)), - ) for pk, title, level in parents] + "pk", "title", "level" + ) + self.lookup_choices = [ + ( + pk, + "%s%s" % ("  " * level, shorten_string(title, max_length=25)), + ) + for pk, title, level in parents + ] def choices(self, cl): yield { - 'selected': self.lookup_val is None, - 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), - 'display': _('All') + "selected": self.lookup_val is None, + "query_string": cl.get_query_string({}, [self.lookup_kwarg]), + "display": _("All"), } for pk, title in self.lookup_choices: yield { - 'selected': pk == int(self.lookup_val or '0'), - 'query_string': cl.get_query_string({self.lookup_kwarg: pk}), - 'display': mark_safe(smart_text(title)) + "selected": pk == int(self.lookup_val or "0"), + "query_string": cl.get_query_string({self.lookup_kwarg: pk}), + "display": mark_safe(smart_text(title)), } def title(self): - return _('Parent') + return _("Parent") class CategoryFieldListFilter(ChoicesFieldListFilter): @@ -67,10 +73,10 @@ class CategoryFieldListFilter(ChoicesFieldListFilter): my_model_field.category_filter = True """ - def __init__(self, f, request, params, model, model_admin, - field_path=None): + def __init__(self, f, request, params, model, model_admin, field_path=None): super(CategoryFieldListFilter, self).__init__( - f, request, params, model, model_admin, field_path) + f, request, params, model, model_admin, field_path + ) # Restrict results to categories which are actually in use: if DJANGO_VERSION < (1, 8): @@ -85,7 +91,7 @@ def __init__(self, f, request, params, model, model_admin, self.lookup_choices = sorted( [ - (i.pk, '%s (%s)' % (i, i._related_count)) + (i.pk, "%s (%s)" % (i, i._related_count)) for i in related_model.objects.annotate( _related_count=Count(related_name) ).exclude(_related_count=0) @@ -95,17 +101,17 @@ def __init__(self, f, request, params, model, model_admin, def choices(self, cl): yield { - 'selected': self.lookup_val is None, - 'query_string': cl.get_query_string({}, [self.lookup_kwarg]), - 'display': _('All') + "selected": self.lookup_val is None, + "query_string": cl.get_query_string({}, [self.lookup_kwarg]), + "display": _("All"), } for pk, title in self.lookup_choices: yield { - 'selected': pk == int(self.lookup_val or '0'), - 'query_string': cl.get_query_string({self.lookup_kwarg: pk}), - 'display': mark_safe(smart_text(title)) + "selected": pk == int(self.lookup_val or "0"), + "query_string": cl.get_query_string({self.lookup_kwarg: pk}), + "display": mark_safe(smart_text(title)), } def title(self): - return _('Category') + return _("Category") diff --git a/feincms/admin/item_editor.py b/feincms/admin/item_editor.py index 051265f6e..9e9fc696b 100644 --- a/feincms/admin/item_editor.py +++ b/feincms/admin/item_editor.py @@ -20,8 +20,8 @@ # ------------------------------------------------------------------------ -FEINCMS_CONTENT_FIELDSET_NAME = 'FEINCMS_CONTENT' -FEINCMS_CONTENT_FIELDSET = (FEINCMS_CONTENT_FIELDSET_NAME, {'fields': ()}) +FEINCMS_CONTENT_FIELDSET_NAME = "FEINCMS_CONTENT" +FEINCMS_CONTENT_FIELDSET = (FEINCMS_CONTENT_FIELDSET_NAME, {"fields": ()}) logger = logging.getLogger(__name__) @@ -45,11 +45,11 @@ class FeinCMSInline(InlineModelAdmin): form = ItemEditorForm extra = 0 - fk_name = 'parent' + fk_name = "parent" if VERSION < (2, 1): - template = 'admin/feincms/content_inline_dj20.html' + template = "admin/feincms/content_inline_dj20.html" else: - template = 'admin/feincms/content_inline.html' + template = "admin/feincms/content_inline.html" # ------------------------------------------------------------------------ @@ -70,7 +70,8 @@ def __init__(self, model, admin_site): def get_inline_instances(self, request, *args, **kwargs): inline_instances = super(ItemEditor, self).get_inline_instances( - request, *args, **kwargs) + request, *args, **kwargs + ) self.append_feincms_inlines(inline_instances, request) return inline_instances @@ -83,9 +84,12 @@ def append_feincms_inlines(self, inline_instances, request): inline_instances.append(inline_instance) def can_add_content(self, request, content_type): - perm = '.'.join(( - content_type._meta.app_label, - get_permission_codename('add', content_type._meta))) + perm = ".".join( + ( + content_type._meta.app_label, + get_permission_codename("add", content_type._meta), + ) + ) return request.user.has_perm(perm) def get_feincms_inlines(self, model, request): @@ -97,27 +101,26 @@ def get_feincms_inlines(self, model, request): if not self.can_add_content(request, content_type): continue - attrs = { - '__module__': model.__module__, - 'model': content_type, - } + attrs = {"__module__": model.__module__, "model": content_type} - if hasattr(content_type, 'feincms_item_editor_inline'): + if hasattr(content_type, "feincms_item_editor_inline"): inline = content_type.feincms_item_editor_inline - attrs['form'] = inline.form + attrs["form"] = inline.form - if hasattr(content_type, 'feincms_item_editor_form'): + if hasattr(content_type, "feincms_item_editor_form"): warnings.warn( - 'feincms_item_editor_form on %s is ignored because ' - 'feincms_item_editor_inline is set too' % content_type, - RuntimeWarning) + "feincms_item_editor_form on %s is ignored because " + "feincms_item_editor_inline is set too" % content_type, + RuntimeWarning, + ) else: inline = FeinCMSInline - attrs['form'] = getattr( - content_type, 'feincms_item_editor_form', inline.form) + attrs["form"] = getattr( + content_type, "feincms_item_editor_form", inline.form + ) - name = '%sFeinCMSInline' % content_type.__name__ + name = "%sFeinCMSInline" % content_type.__name__ # TODO: We generate a new class every time. Is that really wanted? inline_class = type(str(name), (inline,), attrs) inlines.append(inline_class) @@ -129,21 +132,19 @@ def get_content_type_map(self, request): for content_type in self.model._feincms_content_types: if self.model == content_type._feincms_content_class: content_name = content_type._meta.verbose_name - content_types.append( - (content_name, content_type.__name__.lower())) + content_types.append((content_name, content_type.__name__.lower())) return content_types def get_extra_context(self, request): """ Return extra context parameters for add/change views. """ extra_context = { - 'request': request, - 'model': self.model, - 'available_templates': getattr( - self.model, '_feincms_templates', ()), - 'has_parent_attribute': hasattr(self.model, 'parent'), - 'content_types': self.get_content_type_map(request), - 'FEINCMS_CONTENT_FIELDSET_NAME': FEINCMS_CONTENT_FIELDSET_NAME, + "request": request, + "model": self.model, + "available_templates": getattr(self.model, "_feincms_templates", ()), + "has_parent_attribute": hasattr(self.model, "parent"), + "content_types": self.get_content_type_map(request), + "FEINCMS_CONTENT_FIELDSET_NAME": FEINCMS_CONTENT_FIELDSET_NAME, } for processor in self.model.feincms_item_editor_context_processors: @@ -154,9 +155,7 @@ def get_extra_context(self, request): def add_view(self, request, **kwargs): if not self.has_add_permission(request): logger.warning( - "Denied adding %s to \"%s\" (no add permission)", - self.model, - request.user + 'Denied adding %s to "%s" (no add permission)', self.model, request.user ) raise Http404 @@ -164,64 +163,60 @@ def add_view(self, request, **kwargs): # insert dummy object as 'original' so template code can grab defaults # for template, etc. - context['original'] = self.model() + context["original"] = self.model() # If there are errors in the form, we need to preserve the object's # template as it was set when the user attempted to save it, so that # the same regions appear on screen. - if request.method == 'POST' and \ - hasattr(self.model, '_feincms_templates'): - context['original'].template_key = request.POST['template_key'] + if request.method == "POST" and hasattr(self.model, "_feincms_templates"): + context["original"].template_key = request.POST["template_key"] context.update(self.get_extra_context(request)) - context.update(kwargs.get('extra_context', {})) - kwargs['extra_context'] = context + context.update(kwargs.get("extra_context", {})) + kwargs["extra_context"] = context return super(ItemEditor, self).add_view(request, **kwargs) def render_change_form(self, request, context, **kwargs): - if kwargs.get('add'): - if request.method == 'GET' and 'adminform' in context: - if 'template_key' in context['adminform'].form.initial: - context['original'].template_key = ( - context['adminform'].form.initial['template_key']) + if kwargs.get("add"): + if request.method == "GET" and "adminform" in context: + if "template_key" in context["adminform"].form.initial: + context["original"].template_key = context[ + "adminform" + ].form.initial["template_key"] # ensure that initially-selected template in form is also # used to render the initial regions in the item editor - return super( - ItemEditor, self).render_change_form(request, context, **kwargs) + return super(ItemEditor, self).render_change_form(request, context, **kwargs) def change_view(self, request, object_id, **kwargs): obj = self.get_object(request, unquote(object_id)) if not self.has_change_permission(request, obj): logger.warning( - "Denied editing %s to \"%s\" (no edit permission)", + 'Denied editing %s to "%s" (no edit permission)', self.model, - request.user + request.user, ) raise Http404 context = {} context.update(self.get_extra_context(request)) - context.update(kwargs.get('extra_context', {})) - kwargs['extra_context'] = context - return super(ItemEditor, self).change_view( - request, object_id, **kwargs) + context.update(kwargs.get("extra_context", {})) + kwargs["extra_context"] = context + return super(ItemEditor, self).change_view(request, object_id, **kwargs) def save_related(self, request, form, formsets, change): - super(ItemEditor, self).save_related( - request, form, formsets, change) + super(ItemEditor, self).save_related(request, form, formsets, change) itemeditor_post_save_related.send( - sender=form.instance.__class__, - instance=form.instance, - created=not change) + sender=form.instance.__class__, instance=form.instance, created=not change + ) @property def change_form_template(self): opts = self.model._meta return [ - 'admin/feincms/%s/%s/item_editor.html' % ( - opts.app_label, opts.object_name.lower()), - 'admin/feincms/%s/item_editor.html' % opts.app_label, - 'admin/feincms/item_editor.html', + "admin/feincms/%s/%s/item_editor.html" + % (opts.app_label, opts.object_name.lower()), + "admin/feincms/%s/item_editor.html" % opts.app_label, + "admin/feincms/item_editor.html", ] def get_fieldsets(self, request, obj=None): @@ -230,9 +225,7 @@ def get_fieldsets(self, request, obj=None): Is it reasonable to assume this should always be included? """ - fieldsets = copy.deepcopy( - super(ItemEditor, self).get_fieldsets(request, obj) - ) + fieldsets = copy.deepcopy(super(ItemEditor, self).get_fieldsets(request, obj)) names = [f[0] for f in fieldsets] if FEINCMS_CONTENT_FIELDSET_NAME not in names: @@ -247,16 +240,20 @@ def get_fieldsets(self, request, obj=None): recover_form_template = "admin/feincms/recover_form.html" # For Reversion < v2.0.0 - def render_revision_form(self, request, obj, version, context, - revert=False, recover=False): + def render_revision_form( + self, request, obj, version, context, revert=False, recover=False + ): context.update(self.get_extra_context(request)) return super(ItemEditor, self).render_revision_form( - request, obj, version, context, revert, recover) + request, obj, version, context, revert, recover + ) # For Reversion >= v2.0.0 - def _reversion_revisionform_view(self, request, version, template_name, - extra_context=None): + def _reversion_revisionform_view( + self, request, version, template_name, extra_context=None + ): context = extra_context or {} context.update(self.get_extra_context(request)) return super(ItemEditor, self)._reversion_revisionform_view( - request, version, template_name, context) + request, version, template_name, context + ) diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index bff48fa40..2eecb1484 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -14,8 +14,12 @@ from django.contrib.staticfiles.templatetags.staticfiles import static from django.db.models import Q from django.http import ( - HttpResponse, HttpResponseBadRequest, - HttpResponseForbidden, HttpResponseNotFound, HttpResponseServerError) + HttpResponse, + HttpResponseBadRequest, + HttpResponseForbidden, + HttpResponseNotFound, + HttpResponseServerError, +) from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _, ugettext @@ -38,15 +42,14 @@ def django_boolean_icon(field_val, alt_text=None, title=None): """ # Origin: contrib/admin/templatetags/admin_list.py - BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} + BOOLEAN_MAPPING = {True: "yes", False: "no", None: "unknown"} alt_text = alt_text or BOOLEAN_MAPPING[field_val] if title is not None: title = 'title="%s" ' % title else: - title = '' - icon_url = static('feincms/img/icon-%s.gif' % BOOLEAN_MAPPING[field_val]) - return mark_safe( - '%s' % (icon_url, alt_text, title)) + title = "" + icon_url = static("feincms/img/icon-%s.gif" % BOOLEAN_MAPPING[field_val]) + return mark_safe('%s' % (icon_url, alt_text, title)) def _build_tree_structure(queryset): @@ -64,23 +67,16 @@ def _build_tree_structure(queryset): all_nodes = {} mptt_opts = queryset.model._mptt_meta - items = queryset.order_by( - mptt_opts.tree_id_attr, - mptt_opts.left_attr, - ).values_list( - "pk", - "%s_id" % mptt_opts.parent_attr, + items = queryset.order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr).values_list( + "pk", "%s_id" % mptt_opts.parent_attr ) for p_id, parent_id in items: - all_nodes.setdefault( - str(parent_id) if parent_id else 0, - [], - ).append(p_id) + all_nodes.setdefault(str(parent_id) if parent_id else 0, []).append(p_id) return all_nodes # ------------------------------------------------------------------------ -def ajax_editable_boolean_cell(item, attr, text='', override=None): +def ajax_editable_boolean_cell(item, attr, text="", override=None): """ Generate a html snippet for showing a boolean value on the admin page. Item is an object, attr is the attribute name we should display. Text @@ -95,7 +91,7 @@ def ajax_editable_boolean_cell(item, attr, text='', override=None): (useful for "disabled and you can't change it" situations). """ if text: - text = ' (%s)' % text + text = " (%s)" % text if override is not None: a = [django_boolean_icon(override, text), text] @@ -103,15 +99,13 @@ def ajax_editable_boolean_cell(item, attr, text='', override=None): value = getattr(item, attr) a = [ '' % ( - item.pk, - attr, - 'checked="checked"' if value else '', - )] + ' data-inplace-attribute="%s" %s>' + % (item.pk, attr, 'checked="checked"' if value else "") + ] a.insert(0, '
' % (attr, item.pk)) - a.append('
') - return mark_safe(''.join(a)) + a.append("
") + return mark_safe("".join(a)) # ------------------------------------------------------------------------ @@ -127,8 +121,10 @@ class MyTreeEditor(TreeEditor): active_toggle = ajax_editable_boolean('active', _('is active')) """ + def _fn(self, item): return ajax_editable_boolean_cell(item, attr) + _fn.short_description = short_description _fn.editable_boolean_field = attr return _fn @@ -147,8 +143,11 @@ def __init__(self, request, *args, **kwargs): def get_queryset(self, *args, **kwargs): mptt_opts = self.model._mptt_meta - qs = super(ChangeList, self).get_queryset(*args, **kwargs).\ - order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr) + qs = ( + super(ChangeList, self) + .get_queryset(*args, **kwargs) + .order_by(mptt_opts.tree_id_attr, mptt_opts.left_attr) + ) # Force has_filters, so that the expand/collapse in sidebar is visible self.has_filters = True return qs @@ -157,14 +156,15 @@ def get_results(self, request): mptt_opts = self.model._mptt_meta if settings.FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS: clauses = [ - Q(**{ - mptt_opts.tree_id_attr: tree_id, - mptt_opts.left_attr + '__lte': lft, - mptt_opts.right_attr + '__gte': rght, - }) for lft, rght, tree_id in self.queryset.values_list( - mptt_opts.left_attr, - mptt_opts.right_attr, - mptt_opts.tree_id_attr, + Q( + **{ + mptt_opts.tree_id_attr: tree_id, + mptt_opts.left_attr + "__lte": lft, + mptt_opts.right_attr + "__gte": rght, + } + ) + for lft, rght, tree_id in self.queryset.values_list( + mptt_opts.left_attr, mptt_opts.right_attr, mptt_opts.tree_id_attr ) ] # We could optimise a bit here by explicitely filtering out @@ -177,7 +177,7 @@ def get_results(self, request): # clauses if the initial query set is unfiltered. This # is good. self.queryset |= self.model._default_manager.filter( - reduce(lambda p, q: p | q, clauses), + reduce(lambda p, q: p | q, clauses) ) super(ChangeList, self).get_results(request) @@ -186,11 +186,13 @@ def get_results(self, request): # which is not passed in later stages in the tree editor for item in self.result_list: item.feincms_changeable = self.model_admin.has_change_permission( - request, item) + request, item + ) item.feincms_addable = ( - item.feincms_changeable and - self.model_admin.has_add_permission(request, item)) + item.feincms_changeable + and self.model_admin.has_add_permission(request, item) + ) # ------------------------------------------------------------------------ @@ -215,29 +217,32 @@ def __init__(self, *args, **kwargs): self.list_display = list(self.list_display) - if 'indented_short_title' not in self.list_display: - if self.list_display[0] == 'action_checkbox': - self.list_display[1] = 'indented_short_title' + if "indented_short_title" not in self.list_display: + if self.list_display[0] == "action_checkbox": + self.list_display[1] = "indented_short_title" else: - self.list_display[0] = 'indented_short_title' - self.list_display_links = ('indented_short_title',) + self.list_display[0] = "indented_short_title" + self.list_display_links = ("indented_short_title",) opts = self.model._meta self.change_list_template = [ - 'admin/feincms/%s/%s/tree_editor.html' % ( - opts.app_label, opts.object_name.lower()), - 'admin/feincms/%s/tree_editor.html' % opts.app_label, - 'admin/feincms/tree_editor.html', + "admin/feincms/%s/%s/tree_editor.html" + % (opts.app_label, opts.object_name.lower()), + "admin/feincms/%s/tree_editor.html" % opts.app_label, + "admin/feincms/tree_editor.html", ] - self.object_change_permission =\ - opts.app_label + '.' + get_permission_codename('change', opts) - self.object_add_permission =\ - opts.app_label + '.' + get_permission_codename('add', opts) - self.object_delete_permission =\ - opts.app_label + '.' + get_permission_codename('delete', opts) + self.object_change_permission = ( + opts.app_label + "." + get_permission_codename("change", opts) + ) + self.object_add_permission = ( + opts.app_label + "." + get_permission_codename("add", opts) + ) + self.object_delete_permission = ( + opts.app_label + "." + get_permission_codename("delete", opts) + ) def changeable(self, item): - return getattr(item, 'feincms_changeable', True) + return getattr(item, "feincms_changeable", True) def indented_short_title(self, item): """ @@ -245,7 +250,7 @@ def indented_short_title(self, item): the object's depth in the hierarchy. """ mptt_opts = item._mptt_meta - r = '' + r = "" try: url = item.get_absolute_url() except (AttributeError,): @@ -254,31 +259,35 @@ def indented_short_title(self, item): if url: r = ( '') % (url, item.pk) + ' value="%s" id="_refkey_%d" />' + ) % (url, item.pk) - changeable_class = '' + changeable_class = "" if not self.changeable(item): - changeable_class = ' tree-item-not-editable' - tree_root_class = '' + changeable_class = " tree-item-not-editable" + tree_root_class = "" if not item.parent_id: - tree_root_class = ' tree-root' + tree_root_class = " tree-root" r += ( '  ') % ( + ' style="width: %dpx;">  ' + ) % ( item.pk, changeable_class, tree_root_class, - 14 + getattr(item, mptt_opts.level_attr) * 18) + 14 + getattr(item, mptt_opts.level_attr) * 18, + ) -# r += '' - if hasattr(item, 'short_title') and callable(item.short_title): + # r += '' + if hasattr(item, "short_title") and callable(item.short_title): r += escape(item.short_title()) else: - r += escape('%s' % item) -# r += '' + r += escape("%s" % item) + # r += '' return mark_safe(r) - indented_short_title.short_description = _('title') + + indented_short_title.short_description = _("title") def _collect_editable_booleans(self): """ @@ -286,7 +295,7 @@ def _collect_editable_booleans(self): want the user to be able to edit arbitrary fields by crafting an AJAX request by hand. """ - if hasattr(self, '_ajax_editable_booleans'): + if hasattr(self, "_ajax_editable_booleans"): return self._ajax_editable_booleans = {} @@ -299,14 +308,17 @@ def _collect_editable_booleans(self): except (AttributeError, TypeError): continue - attr = getattr(item, 'editable_boolean_field', None) + attr = getattr(item, "editable_boolean_field", None) if attr: - if hasattr(item, 'editable_boolean_result'): + if hasattr(item, "editable_boolean_result"): result_func = item.editable_boolean_result else: + def _fn(attr): return lambda self, instance: [ - ajax_editable_boolean_cell(instance, attr)] + ajax_editable_boolean_cell(instance, attr) + ] + result_func = _fn(attr) self._ajax_editable_booleans[attr] = result_func @@ -315,17 +327,22 @@ def _toggle_boolean(self, request): Handle an AJAX toggle_boolean request """ try: - item_id = int(request.POST.get('item_id', None)) - attr = str(request.POST.get('attr', None)) + item_id = int(request.POST.get("item_id", None)) + attr = str(request.POST.get("attr", None)) except Exception: return HttpResponseBadRequest("Malformed request") if not request.user.is_staff: logger.warning( - "Denied AJAX request by non-staff \"%s\" to toggle boolean" - " %s for object #%s", request.user, attr, item_id) + 'Denied AJAX request by non-staff "%s" to toggle boolean' + " %s for object #%s", + request.user, + attr, + item_id, + ) return HttpResponseForbidden( - _("You do not have permission to modify this object")) + _("You do not have permission to modify this object") + ) self._collect_editable_booleans() @@ -339,15 +356,24 @@ def _toggle_boolean(self, request): if not self.has_change_permission(request, obj=obj): logger.warning( - "Denied AJAX request by \"%s\" to toggle boolean %s for" - " object %s", request.user, attr, item_id) + 'Denied AJAX request by "%s" to toggle boolean %s for' " object %s", + request.user, + attr, + item_id, + ) return HttpResponseForbidden( - _("You do not have permission to modify this object")) + _("You do not have permission to modify this object") + ) new_state = not getattr(obj, attr) logger.info( - "Toggle %s on #%d %s to %s by \"%s\"", - attr, obj.pk, obj, "on" if new_state else "off", request.user) + 'Toggle %s on #%d %s to %s by "%s"', + attr, + obj.pk, + obj, + "on" if new_state else "off", + request.user, + ) try: before_data = self._ajax_editable_booleans[attr](self, obj) @@ -359,17 +385,16 @@ def _toggle_boolean(self, request): data = self._ajax_editable_booleans[attr](self, obj) except Exception: - logger.exception( - "Unhandled exception while toggling %s on %s", attr, obj) - return HttpResponseServerError( - "Unable to toggle %s on %s" % (attr, obj)) + logger.exception("Unhandled exception while toggling %s on %s", attr, obj) + return HttpResponseServerError("Unable to toggle %s on %s" % (attr, obj)) # Weed out unchanged cells to keep the updates small. This assumes # that the order a possible get_descendents() returns does not change # before and after toggling this attribute. Unlikely, but still... return HttpResponse( json.dumps([b for a, b in zip(before_data, data) if a != b]), - content_type="application/json") + content_type="application/json", + ) def get_changelist(self, request, **kwargs): return ChangeList @@ -380,30 +405,36 @@ def changelist_view(self, request, extra_context=None, *args, **kwargs): change list/actions page. """ - if 'actions_column' not in self.list_display: - self.list_display.append('actions_column') + if "actions_column" not in self.list_display: + self.list_display.append("actions_column") # handle common AJAX requests if request.is_ajax(): - cmd = request.POST.get('__cmd') - if cmd == 'toggle_boolean': + cmd = request.POST.get("__cmd") + if cmd == "toggle_boolean": return self._toggle_boolean(request) - elif cmd == 'move_node': + elif cmd == "move_node": return self._move_node(request) - return HttpResponseBadRequest('Oops. AJAX request not understood.') + return HttpResponseBadRequest("Oops. AJAX request not understood.") extra_context = extra_context or {} - extra_context['tree_structure'] = mark_safe( - json.dumps(_build_tree_structure(self.get_queryset(request)))) - extra_context['node_levels'] = mark_safe(json.dumps( - dict(self.get_queryset(request).order_by().values_list( - 'pk', self.model._mptt_meta.level_attr - )) - )) + extra_context["tree_structure"] = mark_safe( + json.dumps(_build_tree_structure(self.get_queryset(request))) + ) + extra_context["node_levels"] = mark_safe( + json.dumps( + dict( + self.get_queryset(request) + .order_by() + .values_list("pk", self.model._mptt_meta.level_attr) + ) + ) + ) return super(TreeEditor, self).changelist_view( - request, extra_context, *args, **kwargs) + request, extra_context, *args, **kwargs + ) def has_add_permission(self, request, obj=None): """ @@ -429,8 +460,7 @@ def has_change_permission(self, request, obj=None): else: r = request.user.has_perm(perm) - return r and super(TreeEditor, self).has_change_permission( - request, obj) + return r and super(TreeEditor, self).has_change_permission(request, obj) def has_delete_permission(self, request, obj=None): """ @@ -443,30 +473,29 @@ def has_delete_permission(self, request, obj=None): else: r = request.user.has_perm(perm) - return r and super(TreeEditor, self).has_delete_permission( - request, obj) + return r and super(TreeEditor, self).has_delete_permission(request, obj) def _move_node(self, request): - if hasattr(self.model.objects, 'move_node'): + if hasattr(self.model.objects, "move_node"): tree_manager = self.model.objects else: tree_manager = self.model._tree_manager queryset = self.get_queryset(request) - cut_item = queryset.get(pk=request.POST.get('cut_item')) - pasted_on = queryset.get(pk=request.POST.get('pasted_on')) - position = request.POST.get('position') + cut_item = queryset.get(pk=request.POST.get("cut_item")) + pasted_on = queryset.get(pk=request.POST.get("pasted_on")) + position = request.POST.get("position") if not self.has_change_permission(request, cut_item): - self.message_user(request, _('No permission')) - return HttpResponse('FAIL') + self.message_user(request, _("No permission")) + return HttpResponse("FAIL") - if position in ('last-child', 'left', 'right'): + if position in ("last-child", "left", "right"): try: tree_manager.move_node(cut_item, pasted_on, position) except InvalidMove as e: - self.message_user(request, '%s' % e) - return HttpResponse('FAIL') + self.message_user(request, "%s" % e) + return HttpResponse("FAIL") # Ensure that model save methods have been run (required to # update Page._cached_url values, might also be helpful for other @@ -475,12 +504,12 @@ def _move_node(self, request): item.save() self.message_user( - request, - ugettext('%s has been moved to a new position.') % cut_item) - return HttpResponse('OK') + request, ugettext("%s has been moved to a new position.") % cut_item + ) + return HttpResponse("OK") - self.message_user(request, _('Did not understand moving instruction.')) - return HttpResponse('FAIL') + self.message_user(request, _("Did not understand moving instruction.")) + return HttpResponse("FAIL") def _actions_column(self, instance): if self.changeable(instance): @@ -488,8 +517,9 @@ def _actions_column(self, instance): return [] def actions_column(self, instance): - return mark_safe(' '.join(self._actions_column(instance))) - actions_column.short_description = _('actions') + return mark_safe(" ".join(self._actions_column(instance))) + + actions_column.short_description = _("actions") def delete_selected_tree(self, modeladmin, request, queryset): """ @@ -498,7 +528,7 @@ def delete_selected_tree(self, modeladmin, request, queryset): trigger the post_delete hooks.) """ # If this is True, the confirmation page has been displayed - if request.POST.get('post'): + if request.POST.get("post"): n = 0 # TODO: The disable_mptt_updates / rebuild is a work around # for what seems to be a mptt problem when deleting items @@ -512,13 +542,15 @@ def delete_selected_tree(self, modeladmin, request, queryset): self.log_deletion(request, obj, obj_display) else: logger.warning( - "Denied delete request by \"%s\" for object #%s", - request.user, obj.id) + 'Denied delete request by "%s" for object #%s', + request.user, + obj.id, + ) if n > 0: queryset.model.objects.rebuild() self.message_user( - request, - _("Successfully deleted %(count)d items.") % {"count": n}) + request, _("Successfully deleted %(count)d items.") % {"count": n} + ) # Return None to display the change list page again return None else: @@ -527,9 +559,10 @@ def delete_selected_tree(self, modeladmin, request, queryset): def get_actions(self, request): actions = super(TreeEditor, self).get_actions(request) - if 'delete_selected' in actions: - actions['delete_selected'] = ( + if "delete_selected" in actions: + actions["delete_selected"] = ( self.delete_selected_tree, - 'delete_selected', - _("Delete selected %(verbose_name_plural)s")) + "delete_selected", + _("Delete selected %(verbose_name_plural)s"), + ) return actions diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index d872b1731..1de3c2571 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -15,15 +15,24 @@ from django.utils.http import http_date from django.utils.safestring import mark_safe from django.utils.translation import get_language, ugettext_lazy as _ + try: from django.urls import ( - NoReverseMatch, reverse, get_script_prefix, set_script_prefix, - Resolver404, resolve, + NoReverseMatch, + reverse, + get_script_prefix, + set_script_prefix, + Resolver404, + resolve, ) except ImportError: from django.core.urlresolvers import ( - NoReverseMatch, reverse, get_script_prefix, set_script_prefix, - Resolver404, resolve, + NoReverseMatch, + reverse, + get_script_prefix, + set_script_prefix, + Resolver404, + resolve, ) from feincms.admin.item_editor import ItemEditorForm @@ -36,9 +45,13 @@ __all__ = ( - 'ApplicationContent', - 'app_reverse', 'app_reverse_lazy', 'permalink', - 'UnpackTemplateResponse', 'standalone', 'unpack', + "ApplicationContent", + "app_reverse", + "app_reverse_lazy", + "permalink", + "UnpackTemplateResponse", + "standalone", + "unpack", ) @@ -47,6 +60,7 @@ class UnpackTemplateResponse(TemplateResponse): Completely the same as marking applicationcontent-contained views with the ``feincms.views.decorators.unpack`` decorator. """ + _feincms_unpack = True @@ -62,6 +76,7 @@ def inner(request, *args, **kwargs): if isinstance(response, HttpResponse): response.standalone = True return response + return wraps(view_func)(inner) @@ -76,19 +91,20 @@ def inner(request, *args, **kwargs): if isinstance(response, TemplateResponse): response._feincms_unpack = True return response + return wraps(view_func)(inner) def cycle_app_reverse_cache(*args, **kwargs): warnings.warn( - 'cycle_app_reverse_cache does nothing and will be removed in' - ' a future version of FeinCMS.', - DeprecationWarning, stacklevel=2, + "cycle_app_reverse_cache does nothing and will be removed in" + " a future version of FeinCMS.", + DeprecationWarning, + stacklevel=2, ) -def app_reverse(viewname, urlconf=None, args=None, kwargs=None, - *vargs, **vkwargs): +def app_reverse(viewname, urlconf=None, args=None, kwargs=None, *vargs, **vkwargs): """ Reverse URLs from application contents @@ -109,9 +125,9 @@ def app_reverse(viewname, urlconf=None, args=None, kwargs=None, # First parameter might be a request instead of an urlconf path, so # we'll try to be helpful and extract the current urlconf from it - extra_context = getattr(urlconf, '_feincms_extra_context', {}) - appconfig = extra_context.get('app_config', {}) - urlconf = appconfig.get('urlconf_path', urlconf) + extra_context = getattr(urlconf, "_feincms_extra_context", {}) + appconfig = extra_context.get("app_config", {}) + urlconf = appconfig.get("urlconf_path", urlconf) appcontent_class = ApplicationContent._feincms_content_models[0] cache_key = appcontent_class.app_reverse_cache_key(urlconf) @@ -124,10 +140,10 @@ def app_reverse(viewname, urlconf=None, args=None, kwargs=None, if urlconf in appcontent_class.ALL_APPS_CONFIG: # We have an overridden URLconf app_config = appcontent_class.ALL_APPS_CONFIG[urlconf] - urlconf = app_config['config'].get('urls', urlconf) + urlconf = app_config["config"].get("urls", urlconf) prefix = content.parent.get_absolute_url() - prefix += '/' if prefix[-1] != '/' else '' + prefix += "/" if prefix[-1] != "/" else "" url_prefix = (urlconf, prefix) cache.set(cache_key, url_prefix, timeout=APP_REVERSE_CACHE_TIMEOUT) @@ -139,11 +155,8 @@ def app_reverse(viewname, urlconf=None, args=None, kwargs=None, try: set_script_prefix(url_prefix[1]) return reverse( - viewname, - url_prefix[0], - args=args, - kwargs=kwargs, - *vargs, **vkwargs) + viewname, url_prefix[0], args=args, kwargs=kwargs, *vargs, **vkwargs + ) finally: set_script_prefix(prefix) @@ -167,8 +180,10 @@ class MyModel(models.Model): def get_absolute_url(self): return ('myapp.urls', 'model_detail', (), {'slug': self.slug}) """ + def inner(*args, **kwargs): return app_reverse(*func(*args, **kwargs)) + return wraps(func)(inner) @@ -182,8 +197,8 @@ class ApplicationContent(models.Model): class Meta: abstract = True - verbose_name = _('application content') - verbose_name_plural = _('application contents') + verbose_name = _("application content") + verbose_name_plural = _("application contents") @classmethod def initialize_type(cls, APPLICATIONS): @@ -192,7 +207,8 @@ def initialize_type(cls, APPLICATIONS): raise ValueError( "APPLICATIONS must be provided with tuples containing at" " least two parameters (urls, name) and an optional extra" - " config dict") + " config dict" + ) urls, name = i[0:2] @@ -202,20 +218,20 @@ def initialize_type(cls, APPLICATIONS): if not isinstance(app_conf, dict): raise ValueError( "The third parameter of an APPLICATIONS entry must be" - " a dict or the name of one!") + " a dict or the name of one!" + ) else: app_conf = {} - cls.ALL_APPS_CONFIG[urls] = { - "urls": urls, - "name": name, - "config": app_conf - } + cls.ALL_APPS_CONFIG[urls] = {"urls": urls, "name": name, "config": app_conf} cls.add_to_class( - 'urlconf_path', - models.CharField(_('application'), max_length=100, choices=[ - (c['urls'], c['name']) for c in cls.ALL_APPS_CONFIG.values()]) + "urlconf_path", + models.CharField( + _("application"), + max_length=100, + choices=[(c["urls"], c["name"]) for c in cls.ALL_APPS_CONFIG.values()], + ), ) class ApplicationContentItemEditorForm(ItemEditorForm): @@ -223,8 +239,7 @@ class ApplicationContentItemEditorForm(ItemEditorForm): custom_fields = {} def __init__(self, *args, **kwargs): - super(ApplicationContentItemEditorForm, self).__init__( - *args, **kwargs) + super(ApplicationContentItemEditorForm, self).__init__(*args, **kwargs) instance = kwargs.get("instance", None) @@ -233,20 +248,20 @@ def __init__(self, *args, **kwargs): # TODO use urlconf_path from POST if set # urlconf_path = request.POST.get('...urlconf_path', # instance.urlconf_path) - self.app_config = cls.ALL_APPS_CONFIG[ - instance.urlconf_path]['config'] + self.app_config = cls.ALL_APPS_CONFIG[instance.urlconf_path][ + "config" + ] except KeyError: self.app_config = {} self.custom_fields = {} - admin_fields = self.app_config.get('admin_fields', {}) + admin_fields = self.app_config.get("admin_fields", {}) if isinstance(admin_fields, dict): self.custom_fields.update(admin_fields) else: get_fields = get_object(admin_fields) - self.custom_fields.update( - get_fields(self, *args, **kwargs)) + self.custom_fields.update(get_fields(self, *args, **kwargs)) params = self.instance.parameters for k, v in self.custom_fields.items(): @@ -262,11 +277,14 @@ def save(self, commit=True, *args, **kwargs): # custom fields before calling save(commit=True) m = super(ApplicationContentItemEditorForm, self).save( - commit=False, *args, **kwargs) + commit=False, *args, **kwargs + ) m.parameters = dict( (k, self.cleaned_data[k]) - for k in self.custom_fields if k in self.cleaned_data) + for k in self.custom_fields + if k in self.cleaned_data + ) if commit: m.save(**kwargs) @@ -279,8 +297,9 @@ def save(self, commit=True, *args, **kwargs): def __init__(self, *args, **kwargs): super(ApplicationContent, self).__init__(*args, **kwargs) - self.app_config = self.ALL_APPS_CONFIG.get( - self.urlconf_path, {}).get('config', {}) + self.app_config = self.ALL_APPS_CONFIG.get(self.urlconf_path, {}).get( + "config", {} + ) def process(self, request, **kw): page_url = self.parent.get_absolute_url() @@ -290,21 +309,20 @@ def process(self, request, **kw): if "path_mapper" in self.app_config: path_mapper = get_object(self.app_config["path_mapper"]) path, page_url = path_mapper( - request.path, - page_url, - appcontent_parameters=self.parameters + request.path, page_url, appcontent_parameters=self.parameters ) else: - path = request._feincms_extra_context['extra_path'] + path = request._feincms_extra_context["extra_path"] # Resolve the module holding the application urls. - urlconf_path = self.app_config.get('urls', self.urlconf_path) + urlconf_path = self.app_config.get("urls", self.urlconf_path) try: fn, args, kwargs = resolve(path, urlconf_path) except (ValueError, Resolver404): - raise Resolver404(str('Not found (resolving %r in %r failed)') % ( - path, urlconf_path)) + raise Resolver404( + str("Not found (resolving %r in %r failed)") % (path, urlconf_path) + ) # Variables from the ApplicationContent parameters are added to request # so we can expose them to our templates via the appcontent_parameters @@ -312,19 +330,14 @@ def process(self, request, **kw): request._feincms_extra_context.update(self.parameters) # Save the application configuration for reuse elsewhere - request._feincms_extra_context.update({ - 'app_config': dict( - self.app_config, - urlconf_path=self.urlconf_path, - ), - }) + request._feincms_extra_context.update( + {"app_config": dict(self.app_config, urlconf_path=self.urlconf_path)} + ) view_wrapper = self.app_config.get("view_wrapper", None) if view_wrapper: fn = partial( - get_object(view_wrapper), - view=fn, - appcontent_parameters=self.parameters + get_object(view_wrapper), view=fn, appcontent_parameters=self.parameters ) output = fn(request, *args, **kwargs) @@ -334,33 +347,32 @@ def process(self, request, **kw): return output elif output.status_code == 200: - if self.unpack(request, output) and 'view' in kw: + if self.unpack(request, output) and "view" in kw: # Handling of @unpack and UnpackTemplateResponse - kw['view'].template_name = output.template_name - kw['view'].request._feincms_extra_context.update( - output.context_data) + kw["view"].template_name = output.template_name + kw["view"].request._feincms_extra_context.update( + output.context_data + ) else: # If the response supports deferred rendering, render the # response right now. We do not handle template response # middleware. - if hasattr(output, 'render') and callable(output.render): + if hasattr(output, "render") and callable(output.render): output.render() - self.rendered_result = mark_safe( - output.content.decode('utf-8')) + self.rendered_result = mark_safe(output.content.decode("utf-8")) self.rendered_headers = {} # Copy relevant headers for later perusal - for h in ('Cache-Control', 'Last-Modified', 'Expires'): + for h in ("Cache-Control", "Last-Modified", "Expires"): if h in output: - self.rendered_headers.setdefault( - h, []).append(output[h]) + self.rendered_headers.setdefault(h, []).append(output[h]) - elif isinstance(output, tuple) and 'view' in kw: - kw['view'].template_name = output[0] - kw['view'].request._feincms_extra_context.update(output[1]) + elif isinstance(output, tuple) and "view" in kw: + kw["view"].template_name = output[0] + kw["view"].request._feincms_extra_context.update(output[1]) else: self.rendered_result = mark_safe(output) @@ -368,25 +380,26 @@ def process(self, request, **kw): return True # successful def send_directly(self, request, response): - mimetype = response.get('Content-Type', 'text/plain') - if ';' in mimetype: - mimetype = mimetype.split(';')[0] + mimetype = response.get("Content-Type", "text/plain") + if ";" in mimetype: + mimetype = mimetype.split(";")[0] mimetype = mimetype.strip() return ( - response.status_code != 200 or - request.is_ajax() or - getattr(response, 'standalone', False) or - mimetype not in ('text/html', 'text/plain')) + response.status_code != 200 + or request.is_ajax() + or getattr(response, "standalone", False) + or mimetype not in ("text/html", "text/plain") + ) def unpack(self, request, response): - return getattr(response, '_feincms_unpack', False) + return getattr(response, "_feincms_unpack", False) def render(self, **kwargs): - return getattr(self, 'rendered_result', '') + return getattr(self, "rendered_result", "") def finalize(self, request, response): - headers = getattr(self, 'rendered_headers', None) + headers = getattr(self, "rendered_headers", None) if headers: self._update_response_headers(request, response, headers) @@ -399,29 +412,29 @@ def _update_response_headers(self, request, response, headers): # Ideally, for the Cache-Control header, we'd want to do some # intelligent combining, but that's hard. Let's just collect and unique # them and let the client worry about that. - cc_headers = set(('must-revalidate',)) - for x in (cc.split(",") for cc in headers.get('Cache-Control', ())): + cc_headers = set(("must-revalidate",)) + for x in (cc.split(",") for cc in headers.get("Cache-Control", ())): cc_headers |= set((s.strip() for s in x)) if len(cc_headers): - response['Cache-Control'] = ", ".join(cc_headers) - else: # Default value - response['Cache-Control'] = 'no-cache, must-revalidate' + response["Cache-Control"] = ", ".join(cc_headers) + else: # Default value + response["Cache-Control"] = "no-cache, must-revalidate" # Check all Last-Modified headers, choose the latest one - lm_list = [parsedate(x) for x in headers.get('Last-Modified', ())] + lm_list = [parsedate(x) for x in headers.get("Last-Modified", ())] if len(lm_list) > 0: - response['Last-Modified'] = http_date(mktime(max(lm_list))) + response["Last-Modified"] = http_date(mktime(max(lm_list))) # Check all Expires headers, choose the earliest one - lm_list = [parsedate(x) for x in headers.get('Expires', ())] + lm_list = [parsedate(x) for x in headers.get("Expires", ())] if len(lm_list) > 0: - response['Expires'] = http_date(mktime(min(lm_list))) + response["Expires"] = http_date(mktime(min(lm_list))) @classmethod def app_reverse_cache_key(self, urlconf_path, **kwargs): - return 'FEINCMS:%s:APPCONTENT:%s:%s' % ( - getattr(settings, 'SITE_ID', 0), + return "FEINCMS:%s:APPCONTENT:%s:%s" % ( + getattr(settings, "SITE_ID", 0), get_language(), urlconf_path, ) @@ -433,17 +446,21 @@ def closest_match(cls, urlconf_path): except AttributeError: page_class = cls.parent.field.rel.to - contents = cls.objects.filter( - parent__in=page_class.objects.active(), - urlconf_path=urlconf_path, - ).order_by('pk').select_related('parent') + contents = ( + cls.objects.filter( + parent__in=page_class.objects.active(), urlconf_path=urlconf_path + ) + .order_by("pk") + .select_related("parent") + ) if len(contents) > 1: try: current = short_language_code(get_language()) return [ - content for content in contents if - short_language_code(content.parent.language) == current + content + for content in contents + if short_language_code(content.parent.language) == current ][0] except (AttributeError, IndexError): diff --git a/feincms/content/contactform/__init__.py b/feincms/content/contactform/__init__.py index f76f4292e..c25396137 100644 --- a/feincms/content/contactform/__init__.py +++ b/feincms/content/contactform/__init__.py @@ -4,5 +4,7 @@ import warnings warnings.warn( - 'The contactform content has been deprecated. Use form-designer instead.', - DeprecationWarning, stacklevel=2) + "The contactform content has been deprecated. Use form-designer instead.", + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/content/contactform/models.py b/feincms/content/contactform/models.py index cfda97377..9bf97a793 100644 --- a/feincms/content/contactform/models.py +++ b/feincms/content/contactform/models.py @@ -18,13 +18,11 @@ class ContactForm(forms.Form): - name = forms.CharField(label=_('name')) - email = forms.EmailField(label=_('email')) - subject = forms.CharField(label=_('subject')) + name = forms.CharField(label=_("name")) + email = forms.EmailField(label=_("email")) + subject = forms.CharField(label=_("subject")) - content = forms.CharField( - widget=forms.Textarea, required=False, - label=_('content')) + content = forms.CharField(widget=forms.Textarea, required=False, label=_("content")) class ContactFormContent(models.Model): @@ -35,8 +33,8 @@ class ContactFormContent(models.Model): class Meta: abstract = True - verbose_name = _('contact form') - verbose_name_plural = _('contact forms') + verbose_name = _("contact form") + verbose_name_plural = _("contact forms") @classmethod def initialize_type(cls, form=None): @@ -44,42 +42,40 @@ def initialize_type(cls, form=None): cls.form = form def process(self, request, **kwargs): - if request.GET.get('_cf_thanks'): + if request.GET.get("_cf_thanks"): self.rendered_output = ct_render_to_string( - 'content/contactform/thanks.html', - {'content': self}, - request=request) + "content/contactform/thanks.html", {"content": self}, request=request + ) return - if request.method == 'POST': + if request.method == "POST": form = self.form(request.POST) if form.is_valid(): send_mail( - form.cleaned_data['subject'], - render_to_string('content/contactform/email.txt', { - 'data': form.cleaned_data, - }), - form.cleaned_data['email'], + form.cleaned_data["subject"], + render_to_string( + "content/contactform/email.txt", {"data": form.cleaned_data} + ), + form.cleaned_data["email"], [self.email], fail_silently=True, ) - return HttpResponseRedirect('?_cf_thanks=1') + return HttpResponseRedirect("?_cf_thanks=1") else: - initial = {'subject': self.subject} + initial = {"subject": self.subject} if request.user.is_authenticated(): - initial['email'] = request.user.email - initial['name'] = request.user.get_full_name() + initial["email"] = request.user.email + initial["name"] = request.user.get_full_name() form = self.form(initial=initial) self.rendered_output = ct_render_to_string( - 'content/contactform/form.html', { - 'content': self, - 'form': form, - }, - request=request) + "content/contactform/form.html", + {"content": self, "form": form}, + request=request, + ) def render(self, **kwargs): - return getattr(self, 'rendered_output', '') + return getattr(self, "rendered_output", "") diff --git a/feincms/content/file/models.py b/feincms/content/file/models.py index fda078a9f..acfab79ec 100644 --- a/feincms/content/file/models.py +++ b/feincms/content/file/models.py @@ -20,21 +20,20 @@ class FileContent(models.Model): title = models.CharField(max_length=200) file = models.FileField( - _('file'), max_length=255, - upload_to=os.path.join(settings.FEINCMS_UPLOAD_PREFIX, 'filecontent')) + _("file"), + max_length=255, + upload_to=os.path.join(settings.FEINCMS_UPLOAD_PREFIX, "filecontent"), + ) class Meta: abstract = True - verbose_name = _('file') - verbose_name_plural = _('files') + verbose_name = _("file") + verbose_name_plural = _("files") def render(self, **kwargs): return ct_render_to_string( - [ - 'content/file/%s.html' % self.region, - 'content/file/default.html', - ], - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + ["content/file/%s.html" % self.region, "content/file/default.html"], + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py index f728f0fa1..351b3e628 100644 --- a/feincms/content/filer/models.py +++ b/feincms/content/filer/models.py @@ -17,17 +17,20 @@ else: __all__ = ( - 'MediaFileContentInline', 'ContentWithFilerFile', - 'FilerFileContent', 'FilerImageContent', + "MediaFileContentInline", + "ContentWithFilerFile", + "FilerFileContent", + "FilerImageContent", ) class MediaFileContentInline(FeinCMSInline): - radio_fields = {'type': admin.VERTICAL} + radio_fields = {"type": admin.VERTICAL} class ContentWithFilerFile(models.Model): """ File content """ + feincms_item_editor_inline = MediaFileContentInline class Meta: @@ -36,25 +39,25 @@ class Meta: def render(self, **kwargs): return ct_render_to_string( [ - 'content/filer/%s_%s.html' % (self.file_type, self.type), - 'content/filer/%s.html' % self.type, - 'content/filer/%s.html' % self.file_type, - 'content/filer/default.html', + "content/filer/%s_%s.html" % (self.file_type, self.type), + "content/filer/%s.html" % self.type, + "content/filer/%s.html" % self.file_type, + "content/filer/default.html", ], - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) class FilerFileContent(ContentWithFilerFile): - mediafile = FilerFileField(verbose_name=_('file'), related_name='+') - file_type = 'file' - type = 'download' + mediafile = FilerFileField(verbose_name=_("file"), related_name="+") + file_type = "file" + type = "download" class Meta: abstract = True - verbose_name = _('file') - verbose_name_plural = _('files') + verbose_name = _("file") + verbose_name_plural = _("files") class FilerImageContent(ContentWithFilerFile): """ @@ -83,36 +86,28 @@ class FilerImageContent(ContentWithFilerFile): must_always_publish_copyright, date_taken, file, id, is_public, url """ - mediafile = FilerImageField(verbose_name=_('image'), related_name='+') - caption = models.CharField( - _('caption'), - max_length=1000, - blank=True, - ) - url = models.CharField( - _('URL'), - max_length=1000, - blank=True, - ) + mediafile = FilerImageField(verbose_name=_("image"), related_name="+") + caption = models.CharField(_("caption"), max_length=1000, blank=True) + url = models.CharField(_("URL"), max_length=1000, blank=True) - file_type = 'image' + file_type = "image" class Meta: abstract = True - verbose_name = _('image') - verbose_name_plural = _('images') + verbose_name = _("image") + verbose_name_plural = _("images") @classmethod def initialize_type(cls, TYPE_CHOICES=None): if TYPE_CHOICES is None: raise ImproperlyConfigured( - 'You have to set TYPE_CHOICES when' - ' creating a %s' % cls.__name__) + "You have to set TYPE_CHOICES when" " creating a %s" % cls.__name__ + ) cls.add_to_class( - 'type', + "type", models.CharField( - _('type'), + _("type"), max_length=20, choices=TYPE_CHOICES, default=TYPE_CHOICES[0][0], diff --git a/feincms/content/image/models.py b/feincms/content/image/models.py index 170236a92..8ddec6c37 100644 --- a/feincms/content/image/models.py +++ b/feincms/content/image/models.py @@ -43,54 +43,59 @@ class ImageContent(models.Model): """ image = models.ImageField( - _('image'), max_length=255, - upload_to=os.path.join(settings.FEINCMS_UPLOAD_PREFIX, 'imagecontent')) + _("image"), + max_length=255, + upload_to=os.path.join(settings.FEINCMS_UPLOAD_PREFIX, "imagecontent"), + ) alt_text = models.CharField( - _('alternate text'), max_length=255, blank=True, - help_text=_('Description of image')) - caption = models.CharField(_('caption'), max_length=255, blank=True) + _("alternate text"), + max_length=255, + blank=True, + help_text=_("Description of image"), + ) + caption = models.CharField(_("caption"), max_length=255, blank=True) class Meta: abstract = True - verbose_name = _('image') - verbose_name_plural = _('images') + verbose_name = _("image") + verbose_name_plural = _("images") def render(self, **kwargs): - templates = ['content/image/default.html'] - if hasattr(self, 'position'): - templates.insert(0, 'content/image/%s.html' % self.position) + templates = ["content/image/default.html"] + if hasattr(self, "position"): + templates.insert(0, "content/image/%s.html" % self.position) return ct_render_to_string( templates, - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) def get_image(self): - type, separator, size = getattr(self, 'format', '').partition(':') + type, separator, size = getattr(self, "format", "").partition(":") if not size: return self.image - thumbnailer = { - 'cropscale': feincms_thumbnail.CropscaleThumbnailer, - }.get(type, feincms_thumbnail.Thumbnailer) + thumbnailer = {"cropscale": feincms_thumbnail.CropscaleThumbnailer}.get( + type, feincms_thumbnail.Thumbnailer + ) return thumbnailer(self.image, size) @classmethod def initialize_type(cls, POSITION_CHOICES=None, FORMAT_CHOICES=None): if POSITION_CHOICES: models.CharField( - _('position'), + _("position"), max_length=10, choices=POSITION_CHOICES, - default=POSITION_CHOICES[0][0] - ).contribute_to_class(cls, 'position') + default=POSITION_CHOICES[0][0], + ).contribute_to_class(cls, "position") if FORMAT_CHOICES: models.CharField( - _('format'), + _("format"), max_length=64, choices=FORMAT_CHOICES, - default=FORMAT_CHOICES[0][0] - ).contribute_to_class(cls, 'format') + default=FORMAT_CHOICES[0][0], + ).contribute_to_class(cls, "format") diff --git a/feincms/content/medialibrary/models.py b/feincms/content/medialibrary/models.py index 9e8d2e30d..bc7d2f474 100644 --- a/feincms/content/medialibrary/models.py +++ b/feincms/content/medialibrary/models.py @@ -6,5 +6,7 @@ from feincms.module.medialibrary.contents import MediaFileContent warnings.warn( - 'Import MediaFileContent from feincms.module.medialibrary.contents.', - DeprecationWarning, stacklevel=2) + "Import MediaFileContent from feincms.module.medialibrary.contents.", + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/content/raw/models.py b/feincms/content/raw/models.py index 487c5a241..283d6c151 100644 --- a/feincms/content/raw/models.py +++ b/feincms/content/raw/models.py @@ -13,12 +13,12 @@ class RawContent(models.Model): snippets too. """ - text = models.TextField(_('content'), blank=True) + text = models.TextField(_("content"), blank=True) class Meta: abstract = True - verbose_name = _('raw content') - verbose_name_plural = _('raw contents') + verbose_name = _("raw content") + verbose_name_plural = _("raw contents") def render(self, **kwargs): return mark_safe(self.text) diff --git a/feincms/content/richtext/models.py b/feincms/content/richtext/models.py index 8ad5a1e9c..a893eb1b4 100644 --- a/feincms/content/richtext/models.py +++ b/feincms/content/richtext/models.py @@ -24,26 +24,21 @@ class RichTextContent(models.Model): feincms_item_editor_context_processors = ( lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, ) - feincms_item_editor_includes = { - 'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE], - } + feincms_item_editor_includes = {"head": [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE]} class Meta: abstract = True - verbose_name = _('rich text') - verbose_name_plural = _('rich texts') + verbose_name = _("rich text") + verbose_name_plural = _("rich texts") def render(self, **kwargs): return ct_render_to_string( - 'content/richtext/default.html', - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + "content/richtext/default.html", + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) @classmethod def initialize_type(cls, cleanse=None): - cls.add_to_class( - 'text', - RichTextField(_('text'), blank=True, cleanse=cleanse), - ) + cls.add_to_class("text", RichTextField(_("text"), blank=True, cleanse=cleanse)) diff --git a/feincms/content/section/models.py b/feincms/content/section/models.py index 44dbdf571..4af9a8b3a 100644 --- a/feincms/content/section/models.py +++ b/feincms/content/section/models.py @@ -15,8 +15,8 @@ class SectionContentInline(FeinCMSInline): - raw_id_fields = ('mediafile',) - radio_fields = {'type': admin.VERTICAL} + raw_id_fields = ("mediafile",) + radio_fields = {"type": admin.VERTICAL} class SectionContent(models.Model): @@ -28,39 +28,46 @@ class SectionContent(models.Model): feincms_item_editor_context_processors = ( lambda x: settings.FEINCMS_RICHTEXT_INIT_CONTEXT, ) - feincms_item_editor_includes = { - 'head': [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE], - } + feincms_item_editor_includes = {"head": [settings.FEINCMS_RICHTEXT_INIT_TEMPLATE]} - title = models.CharField(_('title'), max_length=200, blank=True) - richtext = RichTextField(_('text'), blank=True) + title = models.CharField(_("title"), max_length=200, blank=True) + richtext = RichTextField(_("text"), blank=True) mediafile = MediaFileForeignKey( - MediaFile, on_delete=models.CASCADE, - verbose_name=_('media file'), - related_name='+', blank=True, null=True) + MediaFile, + on_delete=models.CASCADE, + verbose_name=_("media file"), + related_name="+", + blank=True, + null=True, + ) class Meta: abstract = True - verbose_name = _('section') - verbose_name_plural = _('sections') + verbose_name = _("section") + verbose_name_plural = _("sections") @classmethod def initialize_type(cls, TYPE_CHOICES=None, cleanse=None): - if 'feincms.module.medialibrary' not in django_settings.INSTALLED_APPS: + if "feincms.module.medialibrary" not in django_settings.INSTALLED_APPS: raise ImproperlyConfigured( - 'You have to add \'feincms.module.medialibrary\' to your' - ' INSTALLED_APPS before creating a %s' % cls.__name__) + "You have to add 'feincms.module.medialibrary' to your" + " INSTALLED_APPS before creating a %s" % cls.__name__ + ) if TYPE_CHOICES is None: raise ImproperlyConfigured( - 'You need to set TYPE_CHOICES when creating a' - ' %s' % cls.__name__) - - cls.add_to_class('type', models.CharField( - _('type'), - max_length=10, choices=TYPE_CHOICES, - default=TYPE_CHOICES[0][0] - )) + "You need to set TYPE_CHOICES when creating a" " %s" % cls.__name__ + ) + + cls.add_to_class( + "type", + models.CharField( + _("type"), + max_length=10, + choices=TYPE_CHOICES, + default=TYPE_CHOICES[0][0], + ), + ) if cleanse: cls.cleanse = cleanse @@ -68,29 +75,28 @@ def initialize_type(cls, TYPE_CHOICES=None, cleanse=None): @classmethod def get_queryset(cls, filter_args): # Explicitly add nullable FK mediafile to minimize the DB query count - return cls.objects.select_related('parent', 'mediafile').filter( - filter_args) + return cls.objects.select_related("parent", "mediafile").filter(filter_args) def render(self, **kwargs): if self.mediafile: mediafile_type = self.mediafile.type else: - mediafile_type = 'nomedia' + mediafile_type = "nomedia" return ct_render_to_string( [ - 'content/section/%s_%s.html' % (mediafile_type, self.type), - 'content/section/%s.html' % mediafile_type, - 'content/section/%s.html' % self.type, - 'content/section/default.html', + "content/section/%s_%s.html" % (mediafile_type, self.type), + "content/section/%s.html" % mediafile_type, + "content/section/%s.html" % self.type, + "content/section/default.html", ], - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) def save(self, *args, **kwargs): - if getattr(self, 'cleanse', None): + if getattr(self, "cleanse", None): try: # Passes the rich text content as first argument because # the passed callable has been converted into a bound method @@ -101,4 +107,5 @@ def save(self, *args, **kwargs): self.richtext = self.cleanse.im_func(self.richtext) super(SectionContent, self).save(*args, **kwargs) + save.alters_data = True diff --git a/feincms/content/template/models.py b/feincms/content/template/models.py index 26ec26e44..ad203b3d9 100644 --- a/feincms/content/template/models.py +++ b/feincms/content/template/models.py @@ -19,23 +19,23 @@ class TemplateContent(models.Model): ('base.html', 'makes no sense'), ]) """ + class Meta: abstract = True - verbose_name = _('template content') - verbose_name_plural = _('template contents') + verbose_name = _("template content") + verbose_name_plural = _("template contents") @classmethod def initialize_type(cls, TEMPLATES): - cls.add_to_class('template', models.CharField( - _('template'), - max_length=100, - choices=TEMPLATES, - )) + cls.add_to_class( + "template", + models.CharField(_("template"), max_length=100, choices=TEMPLATES), + ) def render(self, **kwargs): return ct_render_to_string( self.template, - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) diff --git a/feincms/content/video/models.py b/feincms/content/video/models.py index 32a4cae81..1e73cf83a 100644 --- a/feincms/content/video/models.py +++ b/feincms/content/video/models.py @@ -20,38 +20,43 @@ class VideoContent(models.Model): """ PORTALS = ( - ('youtube', re.compile(r'youtube'), lambda url: { - 'v': re.search(r'([?&]v=|./././)([^#&]+)', url).group(2), - }), - ('vimeo', re.compile(r'vimeo'), lambda url: { - 'id': re.search(r'/(\d+)', url).group(1), - }), - ('sf', re.compile(r'sf\.tv'), lambda url: { - 'id': re.search(r'/([a-z0-9\-]+)', url).group(1), - }), + ( + "youtube", + re.compile(r"youtube"), + lambda url: {"v": re.search(r"([?&]v=|./././)([^#&]+)", url).group(2)}, + ), + ( + "vimeo", + re.compile(r"vimeo"), + lambda url: {"id": re.search(r"/(\d+)", url).group(1)}, + ), + ( + "sf", + re.compile(r"sf\.tv"), + lambda url: {"id": re.search(r"/([a-z0-9\-]+)", url).group(1)}, + ), ) video = models.URLField( - _('video link'), + _("video link"), help_text=_( - 'This should be a link to a youtube or vimeo video,' - ' i.e.: http://www.youtube.com/watch?v=zmj1rpzDRZ0')) + "This should be a link to a youtube or vimeo video," + " i.e.: http://www.youtube.com/watch?v=zmj1rpzDRZ0" + ), + ) class Meta: abstract = True - verbose_name = _('video') - verbose_name_plural = _('videos') + verbose_name = _("video") + verbose_name_plural = _("videos") def get_context_dict(self): "Extend this if you need more variables passed to template" - return {'content': self, 'portal': 'unknown'} + return {"content": self, "portal": "unknown"} - def get_templates(self, portal='unknown'): + def get_templates(self, portal="unknown"): "Extend/override this if you want to modify the templates used" - return [ - 'content/video/%s.html' % portal, - 'content/video/unknown.html', - ] + return ["content/video/%s.html" % portal, "content/video/unknown.html"] def ctx_for_video(self, vurl): "Get a context dict for a given video URL" @@ -60,7 +65,7 @@ def ctx_for_video(self, vurl): if match.search(vurl): try: ctx.update(context_fn(vurl)) - ctx['portal'] = portal + ctx["portal"] = portal break except AttributeError: continue @@ -69,8 +74,8 @@ def ctx_for_video(self, vurl): def render(self, **kwargs): ctx = self.ctx_for_video(self.video) return ct_render_to_string( - self.get_templates(ctx['portal']), + self.get_templates(ctx["portal"]), ctx, - request=kwargs.get('request'), - context=kwargs.get('context'), + request=kwargs.get("request"), + context=kwargs.get("context"), ) diff --git a/feincms/context_processors.py b/feincms/context_processors.py index 3f5c9b1a7..30bf7de70 100644 --- a/feincms/context_processors.py +++ b/feincms/context_processors.py @@ -7,8 +7,6 @@ def add_page_if_missing(request): """ try: - return { - 'feincms_page': Page.objects.for_request(request, best_match=True), - } + return {"feincms_page": Page.objects.for_request(request, best_match=True)} except Page.DoesNotExist: return {} diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index 37af3bdec..bf8b8ed3b 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -32,11 +32,10 @@ def clean(self, value, *args, **kwargs): return super(JSONFormField, self).clean(value, *args, **kwargs) -if LooseVersion(get_version()) > LooseVersion('1.8'): +if LooseVersion(get_version()) > LooseVersion("1.8"): workaround_class = models.TextField else: - workaround_class = six.with_metaclass( - models.SubfieldBase, models.TextField) + workaround_class = six.with_metaclass(models.SubfieldBase, models.TextField) class JSONField(workaround_class): @@ -54,8 +53,7 @@ def to_python(self, value): if isinstance(value, dict): return value - elif (isinstance(value, six.string_types) or - isinstance(value, six.binary_type)): + elif isinstance(value, six.string_types) or isinstance(value, six.binary_type): # Avoid asking the JSON decoder to handle empty values: if not value: return {} @@ -64,7 +62,8 @@ def to_python(self, value): return json.loads(value) except ValueError: logging.getLogger("feincms.contrib.fields").exception( - "Unable to deserialize store JSONField data: %s", value) + "Unable to deserialize store JSONField data: %s", value + ) return {} else: assert value is None diff --git a/feincms/contrib/preview/urls.py b/feincms/contrib/preview/urls.py index 1f4e324f0..95facb066 100644 --- a/feincms/contrib/preview/urls.py +++ b/feincms/contrib/preview/urls.py @@ -4,6 +4,5 @@ urlpatterns = [ - url(r'^(.*)/_preview/(\d+)/$', PreviewHandler.as_view(), - name='feincms_preview'), + url(r"^(.*)/_preview/(\d+)/$", PreviewHandler.as_view(), name="feincms_preview") ] diff --git a/feincms/contrib/preview/views.py b/feincms/contrib/preview/views.py index e1c37586c..38ba1d6d1 100644 --- a/feincms/contrib/preview/views.py +++ b/feincms/contrib/preview/views.py @@ -29,9 +29,7 @@ def get_object(self): def handler(self, request, *args, **kwargs): if not request.user.is_staff: - raise Http404('Not found (not allowed)') - response = super(PreviewHandler, self).handler( - request, *args, **kwargs) - response['Cache-Control'] =\ - 'no-cache, must-revalidate, no-store, private' + raise Http404("Not found (not allowed)") + response = super(PreviewHandler, self).handler(request, *args, **kwargs) + response["Cache-Control"] = "no-cache, must-revalidate, no-store, private" return response diff --git a/feincms/contrib/richtext.py b/feincms/contrib/richtext.py index 3b9985e55..4e027ea80 100644 --- a/feincms/contrib/richtext.py +++ b/feincms/contrib/richtext.py @@ -6,11 +6,11 @@ class RichTextFormField(forms.fields.CharField): def __init__(self, *args, **kwargs): - self.cleanse = kwargs.pop('cleanse', None) + self.cleanse = kwargs.pop("cleanse", None) super(RichTextFormField, self).__init__(*args, **kwargs) - css_class = self.widget.attrs.get('class', '') - css_class += ' item-richtext' - self.widget.attrs['class'] = css_class + css_class = self.widget.attrs.get("class", "") + css_class += " item-richtext" + self.widget.attrs["class"] = css_class def clean(self, value): value = super(RichTextFormField, self).clean(value) @@ -24,12 +24,12 @@ class RichTextField(models.TextField): Drop-in replacement for Django's ``models.TextField`` which allows editing rich text instead of plain text in the item editor. """ + def __init__(self, *args, **kwargs): - self.cleanse = kwargs.pop('cleanse', None) + self.cleanse = kwargs.pop("cleanse", None) super(RichTextField, self).__init__(*args, **kwargs) def formfield(self, form_class=RichTextFormField, **kwargs): return super(RichTextField, self).formfield( - form_class=form_class, - cleanse=self.cleanse, - **kwargs) + form_class=form_class, cleanse=self.cleanse, **kwargs + ) diff --git a/feincms/contrib/tagging.py b/feincms/contrib/tagging.py index 6520f069c..181f4eed9 100644 --- a/feincms/contrib/tagging.py +++ b/feincms/contrib/tagging.py @@ -19,6 +19,7 @@ from tagging.fields import TagField from tagging.models import Tag from tagging.utils import parse_tag_input + try: from tagging.registry import AlreadyRegistered except ImportError: @@ -27,12 +28,13 @@ # ------------------------------------------------------------------------ def taglist_to_string(taglist): - retval = '' + retval = "" if len(taglist) >= 1: taglist.sort() - retval = ','.join(taglist) + retval = ",".join(taglist) return retval + # ------------------------------------------------------------------------ # The following is lifted from: # http://code.google.com/p/django-tagging/issues/detail?id=189 @@ -57,19 +59,23 @@ def clean(self, value): if VERSION >= (1, 10): + class Tag_formatvalue_mixin(object): def format_value(self, value): - value = parse_tag_input(value or '') + value = parse_tag_input(value or "") return super(Tag_formatvalue_mixin, self).format_value(value) + + else: # _format_value is a private method previous to Django 1.10, # do the job in render() instead to avoid fiddling with # anybody's privates class Tag_formatvalue_mixin(object): def render(self, name, value, attrs=None, *args, **kwargs): - value = parse_tag_input(value or '') + value = parse_tag_input(value or "") return super(Tag_formatvalue_mixin, self).render( - name, value, attrs, *args, **kwargs) + name, value, attrs, *args, **kwargs + ) class fv_FilteredSelectMultiple(Tag_formatvalue_mixin, FilteredSelectMultiple): @@ -87,17 +93,13 @@ def __init__(self, filter_horizontal=False, *args, **kwargs): def formfield(self, **defaults): if self.filter_horizontal: - widget = fv_FilteredSelectMultiple( - self.verbose_name, is_stacked=False) + widget = fv_FilteredSelectMultiple(self.verbose_name, is_stacked=False) else: widget = fv_SelectMultiple() - defaults['widget'] = widget - choices = [( - six.text_type(t), - six.text_type(t)) for t in Tag.objects.all()] - return TagSelectFormField( - choices=choices, required=not self.blank, **defaults) + defaults["widget"] = widget + choices = [(six.text_type(t), six.text_type(t)) for t in Tag.objects.all()] + return TagSelectFormField(choices=choices, required=not self.blank, **defaults) # ------------------------------------------------------------------------ @@ -111,9 +113,15 @@ def pre_save_handler(sender, instance, **kwargs): # ------------------------------------------------------------------------ -def tag_model(cls, admin_cls=None, field_name='tags', sort_tags=False, - select_field=False, auto_add_admin_field=True, - admin_list_display=True): +def tag_model( + cls, + admin_cls=None, + field_name="tags", + sort_tags=False, + select_field=False, + auto_add_admin_field=True, + admin_list_display=True, +): """ tag_model accepts a number of named parameters: @@ -136,14 +144,17 @@ def tag_model(cls, admin_cls=None, field_name='tags', sort_tags=False, except ImportError: from tagging import register as tagging_register - cls.add_to_class(field_name, ( - TagSelectField if select_field else TagField - )(field_name.capitalize(), blank=True)) + cls.add_to_class( + field_name, + (TagSelectField if select_field else TagField)( + field_name.capitalize(), blank=True + ), + ) # use another name for the tag descriptor # See http://code.google.com/p/django-tagging/issues/detail?id=95 for the # reason why try: - tagging_register(cls, tag_descriptor_attr='tagging_' + field_name) + tagging_register(cls, tag_descriptor_attr="tagging_" + field_name) except AlreadyRegistered: return @@ -152,13 +163,11 @@ def tag_model(cls, admin_cls=None, field_name='tags', sort_tags=False, admin_cls.list_display.append(field_name) admin_cls.list_filter.append(field_name) - if auto_add_admin_field and hasattr( - admin_cls, 'add_extension_options'): - admin_cls.add_extension_options(_('Tagging'), { - 'fields': (field_name,) - }) + if auto_add_admin_field and hasattr(admin_cls, "add_extension_options"): + admin_cls.add_extension_options(_("Tagging"), {"fields": (field_name,)}) if sort_tags: pre_save.connect(pre_save_handler, sender=cls) + # ------------------------------------------------------------------------ diff --git a/feincms/default_settings.py b/feincms/default_settings.py index 3cbf95f2c..53c4a41ef 100644 --- a/feincms/default_settings.py +++ b/feincms/default_settings.py @@ -14,42 +14,41 @@ # e.g. 'uploads' if you would prefer /uploads/imagecontent/test.jpg # to /imagecontent/test.jpg. -FEINCMS_UPLOAD_PREFIX = getattr( - settings, - 'FEINCMS_UPLOAD_PREFIX', - '') +FEINCMS_UPLOAD_PREFIX = getattr(settings, "FEINCMS_UPLOAD_PREFIX", "") # ------------------------------------------------------------------------ # Settings for MediaLibrary #: Local path to newly uploaded media files FEINCMS_MEDIALIBRARY_UPLOAD_TO = getattr( - settings, - 'FEINCMS_MEDIALIBRARY_UPLOAD_TO', - 'medialibrary/%Y/%m/') + settings, "FEINCMS_MEDIALIBRARY_UPLOAD_TO", "medialibrary/%Y/%m/" +) #: Thumbnail function for suitable mediafiles. Only receives the media file #: and should return a thumbnail URL (or nothing). FEINCMS_MEDIALIBRARY_THUMBNAIL = getattr( settings, - 'FEINCMS_MEDIALIBRARY_THUMBNAIL', - 'feincms.module.medialibrary.thumbnail.default_admin_thumbnail') + "FEINCMS_MEDIALIBRARY_THUMBNAIL", + "feincms.module.medialibrary.thumbnail.default_admin_thumbnail", +) # ------------------------------------------------------------------------ # Settings for RichText FEINCMS_RICHTEXT_INIT_TEMPLATE = getattr( settings, - 'FEINCMS_RICHTEXT_INIT_TEMPLATE', - 'admin/content/richtext/init_tinymce4.html') + "FEINCMS_RICHTEXT_INIT_TEMPLATE", + "admin/content/richtext/init_tinymce4.html", +) FEINCMS_RICHTEXT_INIT_CONTEXT = getattr( settings, - 'FEINCMS_RICHTEXT_INIT_CONTEXT', { - 'TINYMCE_JS_URL': '//tinymce.cachefly.net/4.2/tinymce.min.js', - 'TINYMCE_DOMAIN': None, - 'TINYMCE_CONTENT_CSS_URL': None, - 'TINYMCE_LINK_LIST_URL': None - } + "FEINCMS_RICHTEXT_INIT_CONTEXT", + { + "TINYMCE_JS_URL": "//tinymce.cachefly.net/4.2/tinymce.min.js", + "TINYMCE_DOMAIN": None, + "TINYMCE_CONTENT_CSS_URL": None, + "TINYMCE_LINK_LIST_URL": None, + }, ) # ------------------------------------------------------------------------ @@ -57,38 +56,29 @@ #: Include ancestors in filtered tree editor lists FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS = getattr( - settings, - 'FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS', - False) + settings, "FEINCMS_TREE_EDITOR_INCLUDE_ANCESTORS", False +) #: Enable checking of object level permissions. Note that if this option is #: enabled, you must plug in an authentication backend that actually does #: implement object level permissions or no page will be editable. FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS = getattr( - settings, - 'FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS', - False) + settings, "FEINCMS_TREE_EDITOR_OBJECT_PERMISSIONS", False +) #: When enabled, the page module is automatically registered with Django's #: default admin site (this is activated by default). -FEINCMS_USE_PAGE_ADMIN = getattr( - settings, - 'FEINCMS_USE_PAGE_ADMIN', - True) +FEINCMS_USE_PAGE_ADMIN = getattr(settings, "FEINCMS_USE_PAGE_ADMIN", True) #: app_label.model_name as per apps.get_model. #: defaults to page.Page FEINCMS_DEFAULT_PAGE_MODEL = getattr( - settings, - 'FEINCMS_DEFAULT_PAGE_MODEL', - 'page.Page') + settings, "FEINCMS_DEFAULT_PAGE_MODEL", "page.Page" +) # ------------------------------------------------------------------------ #: Allow random gunk after a valid page? -FEINCMS_ALLOW_EXTRA_PATH = getattr( - settings, - 'FEINCMS_ALLOW_EXTRA_PATH', - False) +FEINCMS_ALLOW_EXTRA_PATH = getattr(settings, "FEINCMS_ALLOW_EXTRA_PATH", False) # ------------------------------------------------------------------------ #: How to switch languages. @@ -96,10 +86,7 @@ #: and overwrites whatever was set before. #: * ``'EXPLICIT'``: The language set has priority, may only be overridden #: by explicitely a language with ``?set_language=xx``. -FEINCMS_TRANSLATION_POLICY = getattr( - settings, - 'FEINCMS_TRANSLATION_POLICY', - 'STANDARD') +FEINCMS_TRANSLATION_POLICY = getattr(settings, "FEINCMS_TRANSLATION_POLICY", "STANDARD") # ------------------------------------------------------------------------ #: Makes the page handling mechanism try to find a cms page with that @@ -107,60 +94,45 @@ #: customised cms-styled error pages. Do not go overboard, this should #: be as simple and as error resistant as possible, so refrain from #: deeply nested error pages or advanced content types. -FEINCMS_CMS_404_PAGE = getattr( - settings, - 'FEINCMS_CMS_404_PAGE', - None) +FEINCMS_CMS_404_PAGE = getattr(settings, "FEINCMS_CMS_404_PAGE", None) # ------------------------------------------------------------------------ #: When uploading files to the media library, replacing an existing entry, #: try to save the new file under the old file name in order to keep the #: media file path (and thus the media url) constant. #: Experimental, this might not work with all storage backends. -FEINCMS_MEDIAFILE_OVERWRITE = getattr( - settings, - 'FEINCMS_MEDIAFILE_OVERWRITE', - False) +FEINCMS_MEDIAFILE_OVERWRITE = getattr(settings, "FEINCMS_MEDIAFILE_OVERWRITE", False) # ------------------------------------------------------------------------ #: Prefix for thumbnails. Set this to something non-empty to separate thumbs #: from uploads. The value should end with a slash, but this is not enforced. -FEINCMS_THUMBNAIL_DIR = getattr( - settings, - 'FEINCMS_THUMBNAIL_DIR', - '_thumbs/') +FEINCMS_THUMBNAIL_DIR = getattr(settings, "FEINCMS_THUMBNAIL_DIR", "_thumbs/") # ------------------------------------------------------------------------ #: feincms_thumbnail template filter library cache timeout. The default is to #: not cache anything for backwards compatibility. FEINCMS_THUMBNAIL_CACHE_TIMEOUT = getattr( - settings, - 'FEINCMS_THUMBNAIL_CACHE_TIMEOUT', - 0) + settings, "FEINCMS_THUMBNAIL_CACHE_TIMEOUT", 0 +) # ------------------------------------------------------------------------ #: Prevent changing template within admin for pages which have been #: allocated a Template with singleton=True -- template field will become #: read-only for singleton pages. FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED = getattr( - settings, - 'FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED', - False) + settings, "FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED", False +) #: Prevent admin page deletion for pages which have been allocated a #: Template with singleton=True FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED = getattr( - settings, - 'FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED', - False) + settings, "FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED", False +) # ------------------------------------------------------------------------ #: Filter languages available for front end users to this set. This allows #: to have languages not yet ready for prime time while being able to access #: those pages in the admin backend. -FEINCMS_FRONTEND_LANGUAGES = getattr( - settings, - 'FEINCMS_FRONTEND_LANGUAGES', - None) +FEINCMS_FRONTEND_LANGUAGES = getattr(settings, "FEINCMS_FRONTEND_LANGUAGES", None) # ------------------------------------------------------------------------ diff --git a/feincms/extensions/__init__.py b/feincms/extensions/__init__.py index 853f27091..ad37b177f 100644 --- a/feincms/extensions/__init__.py +++ b/feincms/extensions/__init__.py @@ -1,10 +1,15 @@ from __future__ import absolute_import from .base import ( - ExtensionsMixin, Extension, ExtensionModelAdmin, - prefetch_modeladmin_get_queryset) + ExtensionsMixin, + Extension, + ExtensionModelAdmin, + prefetch_modeladmin_get_queryset, +) __all__ = ( - 'ExtensionsMixin', 'Extension', 'ExtensionModelAdmin', - 'prefetch_modeladmin_get_queryset', + "ExtensionsMixin", + "Extension", + "ExtensionModelAdmin", + "prefetch_modeladmin_get_queryset", ) diff --git a/feincms/extensions/base.py b/feincms/extensions/base.py index 31773ff6f..55a0ebde9 100644 --- a/feincms/extensions/base.py +++ b/feincms/extensions/base.py @@ -27,7 +27,7 @@ def register_extensions(cls, *extensions): sufficient. """ - if not hasattr(cls, '_extensions'): + if not hasattr(cls, "_extensions"): cls._extensions = [] cls._extensions_seen = [] @@ -46,32 +46,31 @@ def register_extensions(cls, *extensions): except (AttributeError, ImportError, ValueError): if not extension: raise ImproperlyConfigured( - '%s is not a valid extension for %s' % ( - ext, cls.__name__)) + "%s is not a valid extension for %s" % (ext, cls.__name__) + ) - if hasattr(extension, 'Extension'): + if hasattr(extension, "Extension"): extension = extension.Extension - elif hasattr(extension, 'register'): + elif hasattr(extension, "register"): extension = extension.register - elif hasattr(extension, '__call__'): + elif hasattr(extension, "__call__"): pass else: raise ImproperlyConfigured( - '%s is not a valid extension for %s' % ( - ext, cls.__name__)) + "%s is not a valid extension for %s" % (ext, cls.__name__) + ) if extension in cls._extensions_seen: continue cls._extensions_seen.append(extension) - if hasattr(extension, 'handle_model'): + if hasattr(extension, "handle_model"): cls._extensions.append(extension(cls)) else: - raise ImproperlyConfigured( - '%r is an invalid extension.' % extension) + raise ImproperlyConfigured("%r is an invalid extension." % extension) class Extension(object): @@ -79,8 +78,10 @@ def __init__(self, model, **kwargs): self.model = model for key, value in kwargs.items(): if not hasattr(self, key): - raise TypeError('%s() received an invalid keyword %r' % ( - self.__class__.__name__, key)) + raise TypeError( + "%s() received an invalid keyword %r" + % (self.__class__.__name__, key) + ) setattr(self, key, value) self.handle_model() @@ -98,26 +99,26 @@ def __init__(self, *args, **kwargs): self.initialize_extensions() def initialize_extensions(self): - if not hasattr(self, '_extensions_initialized'): + if not hasattr(self, "_extensions_initialized"): self._extensions_initialized = True - for extension in getattr(self.model, '_extensions', []): + for extension in getattr(self.model, "_extensions", []): extension.handle_modeladmin(self) def add_extension_options(self, *f): if self.fieldsets is None: return - if isinstance(f[-1], dict): # called with a fieldset + if isinstance(f[-1], dict): # called with a fieldset self.fieldsets.insert(self.fieldset_insertion_index, f) - f[1]['classes'] = list(f[1].get('classes', [])) - f[1]['classes'].append('collapse') - elif f: # assume called with "other" fields + f[1]["classes"] = list(f[1].get("classes", [])) + f[1]["classes"].append("collapse") + elif f: # assume called with "other" fields try: - self.fieldsets[1][1]['fields'].extend(f) + self.fieldsets[1][1]["fields"].extend(f) except IndexError: # Fall back to first fieldset if second does not exist # XXX This is really messy. - self.fieldsets[0][1]['fields'].extend(f) + self.fieldsets[0][1]["fields"].extend(f) def extend_list(self, attribute, iterable): extended = list(getattr(self, attribute, ())) @@ -129,12 +130,14 @@ def prefetch_modeladmin_get_queryset(modeladmin, *lookups): """ Wraps default modeladmin ``get_queryset`` to prefetch related lookups. """ + def do_wrap(f): @wraps(f) def wrapper(request, *args, **kwargs): qs = f(request, *args, **kwargs) qs = qs.prefetch_related(*lookups) return qs + return wrapper modeladmin.get_queryset = do_wrap(modeladmin.get_queryset) diff --git a/feincms/extensions/changedate.py b/feincms/extensions/changedate.py index cb21d288a..b470ff387 100644 --- a/feincms/extensions/changedate.py +++ b/feincms/extensions/changedate.py @@ -38,10 +38,14 @@ def dt_to_utc_timestamp(dt): class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('creation_date', models.DateTimeField( - _('creation date'), null=True, editable=False)) - self.model.add_to_class('modification_date', models.DateTimeField( - _('modification date'), null=True, editable=False)) + self.model.add_to_class( + "creation_date", + models.DateTimeField(_("creation date"), null=True, editable=False), + ) + self.model.add_to_class( + "modification_date", + models.DateTimeField(_("modification date"), null=True, editable=False), + ) self.model.last_modified = lambda p: p.modification_date @@ -51,16 +55,17 @@ def handle_model(self): # ------------------------------------------------------------------------ def last_modified_response_processor(page, request, response): # Don't include Last-Modified if we don't want to be cached - if "no-cache" in response.get('Cache-Control', ''): + if "no-cache" in response.get("Cache-Control", ""): return # If we already have a Last-Modified, take the later one last_modified = dt_to_utc_timestamp(page.last_modified()) - if response.has_header('Last-Modified'): + if response.has_header("Last-Modified"): last_modified = max( - last_modified, - mktime_tz(parsedate_tz(response['Last-Modified']))) + last_modified, mktime_tz(parsedate_tz(response["Last-Modified"])) + ) + + response["Last-Modified"] = http_date(last_modified) - response['Last-Modified'] = http_date(last_modified) # ------------------------------------------------------------------------ diff --git a/feincms/extensions/ct_tracker.py b/feincms/extensions/ct_tracker.py index 51308e5f8..9f263c535 100644 --- a/feincms/extensions/ct_tracker.py +++ b/feincms/extensions/ct_tracker.py @@ -46,34 +46,37 @@ def _fetch_content_type_counts(self): empty _ct_inventory. """ - if 'counts' not in self._cache: - if (self.item._ct_inventory and - self.item._ct_inventory.get('_version_', -1) == - INVENTORY_VERSION): + if "counts" not in self._cache: + if ( + self.item._ct_inventory + and self.item._ct_inventory.get("_version_", -1) == INVENTORY_VERSION + ): try: - self._cache['counts'] = self._from_inventory( - self.item._ct_inventory) + self._cache["counts"] = self._from_inventory( + self.item._ct_inventory + ) except KeyError: # It's possible that the inventory does not fit together # with the current models anymore, f.e. because a content # type has been removed. pass - if 'counts' not in self._cache: + if "counts" not in self._cache: super(TrackerContentProxy, self)._fetch_content_type_counts() - self.item._ct_inventory = self._to_inventory( - self._cache['counts']) + self.item._ct_inventory = self._to_inventory(self._cache["counts"]) self.item.__class__.objects.filter(id=self.item.id).update( - _ct_inventory=self.item._ct_inventory) + _ct_inventory=self.item._ct_inventory + ) # Run post save handler by hand - if hasattr(self.item, 'get_descendants'): + if hasattr(self.item, "get_descendants"): self.item.get_descendants(include_self=False).update( - _ct_inventory=None) - return self._cache['counts'] + _ct_inventory=None + ) + return self._cache["counts"] def _translation_map(self): cls = self.item.__class__ @@ -101,20 +104,20 @@ def _from_inventory(self, inventory): map = self._translation_map() - return dict((region, [ - (pk, map[-ct]) for pk, ct in items - ]) for region, items in inventory.items() if region != '_version_') + return dict( + (region, [(pk, map[-ct]) for pk, ct in items]) + for region, items in inventory.items() + if region != "_version_" + ) def _to_inventory(self, counts): map = self._translation_map() inventory = dict( - ( - region, - [(pk, map[ct]) for pk, ct in items], - ) for region, items in counts.items() + (region, [(pk, map[ct]) for pk, ct in items]) + for region, items in counts.items() ) - inventory['_version_'] = INVENTORY_VERSION + inventory["_version_"] = INVENTORY_VERSION return inventory @@ -151,12 +154,15 @@ def single_pre_save_handler(sender, instance, **kwargs): # ------------------------------------------------------------------------ class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('_ct_inventory', JSONField( - _('content types'), editable=False, blank=True, null=True)) + self.model.add_to_class( + "_ct_inventory", + JSONField(_("content types"), editable=False, blank=True, null=True), + ) self.model.content_proxy_class = TrackerContentProxy pre_save.connect(single_pre_save_handler, sender=self.model) - if hasattr(self.model, 'get_descendants'): + if hasattr(self.model, "get_descendants"): post_save.connect(tree_post_save_handler, sender=self.model) + # ------------------------------------------------------------------------ diff --git a/feincms/extensions/datepublisher.py b/feincms/extensions/datepublisher.py index 39c26f6d5..bae2387d8 100644 --- a/feincms/extensions/datepublisher.py +++ b/feincms/extensions/datepublisher.py @@ -24,7 +24,7 @@ # ------------------------------------------------------------------------ -def format_date(d, if_none=''): +def format_date(d, if_none=""): """ Format a date in a nice human readable way: Omit the year if it's the current year. Also return a default value if no date is passed in. @@ -34,12 +34,12 @@ def format_date(d, if_none=''): return if_none now = timezone.now() - fmt = (d.year == now.year) and '%d.%m' or '%d.%m.%Y' + fmt = (d.year == now.year) and "%d.%m" or "%d.%m.%Y" return d.strftime(fmt) def latest_children(self): - return self.get_children().order_by('-publication_date') + return self.get_children().order_by("-publication_date") # ------------------------------------------------------------------------ @@ -70,8 +70,8 @@ def granular_now(n=None, default_tz=None): retval = timezone.make_aware(d, default_tz, is_dst=False) except TypeError: # Pre-Django 1.9 retval = timezone.make_aware( - datetime(n.year, n.month, n.day, n.hour + 1, rounded_minute), - default_tz) + datetime(n.year, n.month, n.day, n.hour + 1, rounded_minute), default_tz + ) return retval @@ -95,16 +95,19 @@ def datepublisher_response_processor(page, request, response): class Extension(extensions.Extension): def handle_model(self): self.model.add_to_class( - 'publication_date', - models.DateTimeField(_('publication date'), default=granular_now)) + "publication_date", + models.DateTimeField(_("publication date"), default=granular_now), + ) self.model.add_to_class( - 'publication_end_date', + "publication_end_date", models.DateTimeField( - _('publication end date'), - blank=True, null=True, - help_text=_( - 'Leave empty if the entry should stay active forever.'))) - self.model.add_to_class('latest_children', latest_children) + _("publication end date"), + blank=True, + null=True, + help_text=_("Leave empty if the entry should stay active forever."), + ), + ) + self.model.add_to_class("latest_children", latest_children) # Patch in rounding the pub and pub_end dates on save orig_save = self.model.save @@ -113,44 +116,52 @@ def granular_save(obj, *args, **kwargs): if obj.publication_date: obj.publication_date = granular_now(obj.publication_date) if obj.publication_end_date: - obj.publication_end_date = granular_now( - obj.publication_end_date) + obj.publication_end_date = granular_now(obj.publication_end_date) orig_save(obj, *args, **kwargs) + self.model.save = granular_save # Append publication date active check - if hasattr(self.model._default_manager, 'add_to_active_filters'): + if hasattr(self.model._default_manager, "add_to_active_filters"): self.model._default_manager.add_to_active_filters( lambda queryset: queryset.filter( - Q(publication_date__lte=granular_now()) & - (Q(publication_end_date__isnull=True) | - Q(publication_end_date__gt=granular_now()))), - key='datepublisher', + Q(publication_date__lte=granular_now()) + & ( + Q(publication_end_date__isnull=True) + | Q(publication_end_date__gt=granular_now()) + ) + ), + key="datepublisher", ) # Processor to patch up response headers for expiry date - self.model.register_response_processor( - datepublisher_response_processor) + self.model.register_response_processor(datepublisher_response_processor) def handle_modeladmin(self, modeladmin): def datepublisher_admin(self, obj): - return mark_safe('%s – %s' % ( - format_date(obj.publication_date), - format_date(obj.publication_end_date, '∞'), - )) - datepublisher_admin.short_description = _('visible from - to') + return mark_safe( + "%s – %s" + % ( + format_date(obj.publication_date), + format_date(obj.publication_end_date, "∞"), + ) + ) + + datepublisher_admin.short_description = _("visible from - to") modeladmin.__class__.datepublisher_admin = datepublisher_admin try: - pos = modeladmin.list_display.index('is_visible_admin') + pos = modeladmin.list_display.index("is_visible_admin") except ValueError: pos = len(modeladmin.list_display) - modeladmin.list_display.insert(pos + 1, 'datepublisher_admin') + modeladmin.list_display.insert(pos + 1, "datepublisher_admin") + + modeladmin.add_extension_options( + _("Date-based publishing"), + {"fields": ["publication_date", "publication_end_date"]}, + ) - modeladmin.add_extension_options(_('Date-based publishing'), { - 'fields': ['publication_date', 'publication_end_date'], - }) # ------------------------------------------------------------------------ diff --git a/feincms/extensions/featured.py b/feincms/extensions/featured.py index 139edcdda..9d5f2e92d 100644 --- a/feincms/extensions/featured.py +++ b/feincms/extensions/featured.py @@ -13,15 +13,10 @@ class Extension(extensions.Extension): def handle_model(self): self.model.add_to_class( - 'featured', - models.BooleanField( - _('featured'), - default=False, - ), + "featured", models.BooleanField(_("featured"), default=False) ) def handle_modeladmin(self, modeladmin): - modeladmin.add_extension_options(_('Featured'), { - 'fields': ('featured',), - 'classes': ('collapse',), - }) + modeladmin.add_extension_options( + _("Featured"), {"fields": ("featured",), "classes": ("collapse",)} + ) diff --git a/feincms/extensions/seo.py b/feincms/extensions/seo.py index f71d9f5f3..3f01152a0 100644 --- a/feincms/extensions/seo.py +++ b/feincms/extensions/seo.py @@ -12,24 +12,31 @@ class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('meta_keywords', models.TextField( - _('meta keywords'), - blank=True, - help_text=_('Keywords are ignored by most search engines.'))) - self.model.add_to_class('meta_description', models.TextField( - _('meta description'), - blank=True, - help_text=_('This text is displayed on the search results page. ' - 'It is however not used for the SEO ranking. ' - 'Text longer than 140 characters is truncated.'))) + self.model.add_to_class( + "meta_keywords", + models.TextField( + _("meta keywords"), + blank=True, + help_text=_("Keywords are ignored by most search engines."), + ), + ) + self.model.add_to_class( + "meta_description", + models.TextField( + _("meta description"), + blank=True, + help_text=_( + "This text is displayed on the search results page. " + "It is however not used for the SEO ranking. " + "Text longer than 140 characters is truncated." + ), + ), + ) def handle_modeladmin(self, modeladmin): - modeladmin.extend_list( - 'search_fields', - ['meta_keywords', 'meta_description'], - ) + modeladmin.extend_list("search_fields", ["meta_keywords", "meta_description"]) - modeladmin.add_extension_options(_('Search engine optimization'), { - 'fields': ('meta_keywords', 'meta_description'), - 'classes': ('collapse',), - }) + modeladmin.add_extension_options( + _("Search engine optimization"), + {"fields": ("meta_keywords", "meta_description"), "classes": ("collapse",)}, + ) diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index 4c37cda1d..bbcda6d15 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -36,7 +36,7 @@ logger = logging.getLogger(__name__) LANGUAGE_COOKIE_NAME = django_settings.LANGUAGE_COOKIE_NAME -if hasattr(translation, 'LANGUAGE_SESSION_KEY'): +if hasattr(translation, "LANGUAGE_SESSION_KEY"): LANGUAGE_SESSION_KEY = translation.LANGUAGE_SESSION_KEY else: # Django 1.6 @@ -50,8 +50,10 @@ def user_has_language_set(request): This is taken later on as an indication that we should not mess with the site's language settings, after all, the user's decision is what counts. """ - if (hasattr(request, 'session') and - request.session.get(LANGUAGE_SESSION_KEY) is not None): + if ( + hasattr(request, "session") + and request.session.get(LANGUAGE_SESSION_KEY) is not None + ): return True if LANGUAGE_COOKIE_NAME in request.COOKIES: return True @@ -91,19 +93,18 @@ def translation_set_language(request, select_language): translation.activate(select_language) request.LANGUAGE_CODE = translation.get_language() - if hasattr(request, 'session'): + if hasattr(request, "session"): # User has a session, then set this language there if select_language != request.session.get(LANGUAGE_SESSION_KEY): request.session[LANGUAGE_SESSION_KEY] = select_language - elif request.method == 'GET' and not fallback: + elif request.method == "GET" and not fallback: # No session is active. We need to set a cookie for the language # so that it persists when users change their location to somewhere # not under the control of the CMS. # Only do this when request method is GET (mainly, do not abort # POST requests) response = HttpResponseRedirect(request.get_full_path()) - response.set_cookie( - str(LANGUAGE_COOKIE_NAME), select_language) + response.set_cookie(str(LANGUAGE_COOKIE_NAME), select_language) return response @@ -118,8 +119,8 @@ def translations_request_processor_explicit(page, request): desired_language = page.language # ...except if the user explicitely wants to switch language - if 'set_language' in request.GET: - desired_language = request.GET['set_language'] + if "set_language" in request.GET: + desired_language = request.GET["set_language"] # ...or the user already has explicitely set a language, bail out and # don't change it for them behind their back elif user_has_language_set(request): @@ -131,7 +132,7 @@ def translations_request_processor_explicit(page, request): # ------------------------------------------------------------------------ def translations_request_processor_standard(page, request): # If this page is just a redirect, don't do any language specific setup - if getattr(page, 'redirect_to', None): + if getattr(page, "redirect_to", None): return if page.language == translation.get_language(): @@ -142,51 +143,54 @@ def translations_request_processor_standard(page, request): # ------------------------------------------------------------------------ def get_current_language_code(request): - language_code = getattr(request, 'LANGUAGE_CODE', None) + language_code = getattr(request, "LANGUAGE_CODE", None) if language_code is None: logger.warning( "Could not access request.LANGUAGE_CODE. Is 'django.middleware." - "locale.LocaleMiddleware' in MIDDLEWARE_CLASSES?") + "locale.LocaleMiddleware' in MIDDLEWARE_CLASSES?" + ) return language_code # ------------------------------------------------------------------------ class Extension(extensions.Extension): - def handle_model(self): cls = self.model cls.add_to_class( - 'language', + "language", models.CharField( - _('language'), + _("language"), max_length=10, choices=django_settings.LANGUAGES, - default=django_settings.LANGUAGES[0][0])) + default=django_settings.LANGUAGES[0][0], + ), + ) cls.add_to_class( - 'translation_of', + "translation_of", models.ForeignKey( - 'self', + "self", on_delete=models.CASCADE, - blank=True, null=True, verbose_name=_('translation of'), - related_name='translations', - limit_choices_to={'language': django_settings.LANGUAGES[0][0]}, - help_text=_( - 'Leave this empty for entries in the primary language.'), - ) + blank=True, + null=True, + verbose_name=_("translation of"), + related_name="translations", + limit_choices_to={"language": django_settings.LANGUAGES[0][0]}, + help_text=_("Leave this empty for entries in the primary language."), + ), ) - if hasattr(cls, 'register_request_processor'): + if hasattr(cls, "register_request_processor"): if settings.FEINCMS_TRANSLATION_POLICY == "EXPLICIT": cls.register_request_processor( - translations_request_processor_explicit, - key='translations') + translations_request_processor_explicit, key="translations" + ) else: # STANDARD cls.register_request_processor( - translations_request_processor_standard, - key='translations') + translations_request_processor_standard, key="translations" + ) - if hasattr(cls, 'get_redirect_to_target'): + if hasattr(cls, "get_redirect_to_target"): original_get_redirect_to_target = cls.get_redirect_to_target @monkeypatch_method(cls) @@ -199,7 +203,7 @@ def get_redirect_to_target(self, request=None): redirection. """ target = original_get_redirect_to_target(self, request) - if target and target.find('//') == -1: + if target and target.find("//") == -1: # Not an offsite link http://bla/blubb try: page = cls.objects.page_for_path(target) @@ -217,9 +221,10 @@ def available_translations(self): if not self.id: # New, unsaved pages have no translations return [] - if hasattr(cls.objects, 'apply_active_filters'): + if hasattr(cls.objects, "apply_active_filters"): filter_active = cls.objects.apply_active_filters else: + def filter_active(queryset): return queryset @@ -228,9 +233,10 @@ def filter_active(queryset): elif self.translation_of: # reuse prefetched queryset, do not filter it res = [ - t for t - in filter_active(self.translation_of.translations.all()) - if t.language != self.language] + t + for t in filter_active(self.translation_of.translations.all()) + if t.language != self.language + ] res.insert(0, self.translation_of) return res else: @@ -244,7 +250,10 @@ def get_original_translation(self, *args, **kwargs): return self.translation_of logger.debug( "Page pk=%d (%s) has no primary language translation (%s)", - self.pk, self.language, django_settings.LANGUAGES[0][0]) + self.pk, + self.language, + django_settings.LANGUAGES[0][0], + ) return self @monkeypatch_property(cls) @@ -253,12 +262,12 @@ def original_translation(self): @monkeypatch_method(cls) def get_translation(self, language): - return self.original_translation.translations.get( - language=language) + return self.original_translation.translations.get(language=language) def handle_modeladmin(self, modeladmin): extensions.prefetch_modeladmin_get_queryset( - modeladmin, 'translation_of__translations', 'translations') + modeladmin, "translation_of__translations", "translations" + ) def available_translations_admin(self, page): # Do not use available_translations() because we don't care @@ -268,10 +277,7 @@ def available_translations_admin(self, page): if page.translation_of: translations.append(page.translation_of) translations.extend(page.translation_of.translations.all()) - translations = { - p.language: p.id - for p in translations - } + translations = {p.language: p.id for p in translations} links = [] @@ -280,33 +286,30 @@ def available_translations_admin(self, page): continue if key in translations: - links.append('%s' % ( - translations[key], _('Edit translation'), key.upper())) + links.append( + '%s' + % (translations[key], _("Edit translation"), key.upper()) + ) else: links.append( '%s' % ( - page.id, - key, - _('Create translation'), - key.upper() - ) + '%s&language=%s" title="%s">%s' + % (page.id, key, _("Create translation"), key.upper()) ) - return mark_safe(' | '.join(links)) + return mark_safe(" | ".join(links)) - available_translations_admin.short_description = _('translations') - modeladmin.__class__.available_translations_admin =\ - available_translations_admin + available_translations_admin.short_description = _("translations") + modeladmin.__class__.available_translations_admin = available_translations_admin - if hasattr(modeladmin, 'add_extension_options'): - modeladmin.add_extension_options('language', 'translation_of') + if hasattr(modeladmin, "add_extension_options"): + modeladmin.add_extension_options("language", "translation_of") modeladmin.extend_list( - 'list_display', - ['language', 'available_translations_admin'], + "list_display", ["language", "available_translations_admin"] ) - modeladmin.extend_list('list_filter', ['language']) - modeladmin.extend_list('raw_id_fields', ['translation_of']) + modeladmin.extend_list("list_filter", ["language"]) + modeladmin.extend_list("raw_id_fields", ["translation_of"]) + # ------------------------------------------------------------------------ diff --git a/feincms/management/commands/medialibrary_orphans.py b/feincms/management/commands/medialibrary_orphans.py index 7a27a56de..d1c1eb00b 100644 --- a/feincms/management/commands/medialibrary_orphans.py +++ b/feincms/management/commands/medialibrary_orphans.py @@ -12,10 +12,10 @@ class Command(NoArgsCommand): help = "Prints all orphaned files in the `media/medialibrary` folder" def handle_noargs(self, **options): - mediafiles = list(MediaFile.objects.values_list('file', flat=True)) + mediafiles = list(MediaFile.objects.values_list("file", flat=True)) # TODO make this smarter, and take MEDIA_ROOT into account - for base, dirs, files in os.walk('media/medialibrary'): + for base, dirs, files in os.walk("media/medialibrary"): for f in files: full = os.path.join(base[6:], f) if force_text(full) not in mediafiles: diff --git a/feincms/management/commands/medialibrary_to_filer.py b/feincms/management/commands/medialibrary_to_filer.py index ff34c8d41..2ac3fefec 100644 --- a/feincms/management/commands/medialibrary_to_filer.py +++ b/feincms/management/commands/medialibrary_to_filer.py @@ -17,31 +17,31 @@ PageFilerImageContent = Page.content_type_for(FilerImageContent) -assert all(( - PageMediaFileContent, - PageFilerFileContent, - PageFilerImageContent)), 'Not all required models available' +assert all( + (PageMediaFileContent, PageFilerFileContent, PageFilerImageContent) +), "Not all required models available" class Command(NoArgsCommand): help = "Migrate the medialibrary and contents to django-filer" def handle_noargs(self, **options): - user = User.objects.order_by('pk')[0] + user = User.objects.order_by("pk")[0] count = MediaFile.objects.count() - for i, mediafile in enumerate(MediaFile.objects.order_by('pk')): - model = Image if mediafile.type == 'image' else File - content_model = PageFilerImageContent if mediafile.type == 'image' else PageFilerFileContent # noqa + for i, mediafile in enumerate(MediaFile.objects.order_by("pk")): + model = Image if mediafile.type == "image" else File + content_model = ( + PageFilerImageContent + if mediafile.type == "image" + else PageFilerFileContent + ) # noqa filerfile = model.objects.create( owner=user, original_filename=mediafile.file.name, - file=DjangoFile( - mediafile.file.file, - name=mediafile.file.name, - ), + file=DjangoFile(mediafile.file.file, name=mediafile.file.name), ) contents = PageMediaFileContent.objects.filter(mediafile=mediafile) @@ -58,6 +58,6 @@ def handle_noargs(self, **options): content.delete() if not i % 10: - self.stdout.write('%s / %s files\n' % (i, count)) + self.stdout.write("%s / %s files\n" % (i, count)) - self.stdout.write('%s / %s files\n' % (count, count)) + self.stdout.write("%s / %s files\n" % (count, count)) diff --git a/feincms/management/commands/rebuild_mptt.py b/feincms/management/commands/rebuild_mptt.py index b99291d02..f290daa50 100644 --- a/feincms/management/commands/rebuild_mptt.py +++ b/feincms/management/commands/rebuild_mptt.py @@ -19,12 +19,10 @@ class Command(BaseCommand): - help = ( - "Run this manually to rebuild your mptt pointers. Only use in" - " emergencies.") + help = "Run this manually to rebuild your mptt pointers. Only use in emergencies." def handle_noargs(self, **options): - self.handle(**options) + self.handle(**options) def handle(self, **options): self.stdout.write("Rebuilding MPTT pointers for Page") diff --git a/feincms/models.py b/feincms/models.py index 0e3d49b2c..0bcddabfc 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -36,7 +36,7 @@ class Region(object): def __init__(self, key, title, *args): self.key = key self.title = title - self.inherited = args and args[0] == 'inherited' or False + self.inherited = args and args[0] == "inherited" or False self._content_types = [] def __str__(self): @@ -50,8 +50,7 @@ def content_types(self): """ return [ - (ct.__name__.lower(), ct._meta.verbose_name) - for ct in self._content_types + (ct.__name__.lower(), ct._meta.verbose_name) for ct in self._content_types ] @@ -62,8 +61,7 @@ class Template(object): CMS object, most commonly a page. """ - def __init__(self, title, path, regions, key=None, preview_image=None, - **kwargs): + def __init__(self, title, path, regions, key=None, preview_image=None, **kwargs): # The key is what will be stored in the database. If key is undefined # use the template path as fallback. if not key: @@ -73,10 +71,10 @@ def __init__(self, title, path, regions, key=None, preview_image=None, self.title = title self.path = path self.preview_image = preview_image - self.singleton = kwargs.get('singleton', False) - self.child_template = kwargs.get('child_template', None) - self.enforce_leaf = kwargs.get('enforce_leaf', False) - self.urlconf = kwargs.get('urlconf', None) + self.singleton = kwargs.get("singleton", False) + self.child_template = kwargs.get("child_template", None) + self.enforce_leaf = kwargs.get("enforce_leaf", False) + self.urlconf = kwargs.get("urlconf", None) def _make_region(data): if isinstance(data, Region): @@ -105,9 +103,7 @@ def __init__(self, item): item._needs_content_types() self.item = item self.db = item._state.db - self._cache = { - 'cts': {}, - } + self._cache = {"cts": {}} def _inherit_from(self): """ @@ -118,8 +114,7 @@ def _inherit_from(self): is good enough (tm) for pages. """ - return self.item.get_ancestors(ascending=True).values_list( - 'pk', flat=True) + return self.item.get_ancestors(ascending=True).values_list("pk", flat=True) def _fetch_content_type_counts(self): """ @@ -138,7 +133,7 @@ def _fetch_content_type_counts(self): } """ - if 'counts' not in self._cache: + if "counts" not in self._cache: counts = self._fetch_content_type_count_helper(self.item.pk) empty_inherited_regions = set() @@ -149,7 +144,8 @@ def _fetch_content_type_counts(self): if empty_inherited_regions: for parent in self._inherit_from(): parent_counts = self._fetch_content_type_count_helper( - parent, regions=tuple(empty_inherited_regions)) + parent, regions=tuple(empty_inherited_regions) + ) counts.update(parent_counts) for key in parent_counts.keys(): @@ -158,28 +154,27 @@ def _fetch_content_type_counts(self): if not empty_inherited_regions: break - self._cache['counts'] = counts - return self._cache['counts'] + self._cache["counts"] = counts + return self._cache["counts"] def _fetch_content_type_count_helper(self, pk, regions=None): - tmpl = [ - 'SELECT %d AS ct_idx, region, COUNT(id) FROM %s WHERE parent_id=%s' - ] + tmpl = ["SELECT %d AS ct_idx, region, COUNT(id) FROM %s WHERE parent_id=%s"] args = [] if regions: - tmpl.append( - 'AND region IN (' + ','.join(['%%s'] * len(regions)) + ')') + tmpl.append("AND region IN (" + ",".join(["%%s"] * len(regions)) + ")") args.extend(regions * len(self.item._feincms_content_types)) - tmpl.append('GROUP BY region') - tmpl = ' '.join(tmpl) + tmpl.append("GROUP BY region") + tmpl = " ".join(tmpl) - sql = ' UNION '.join([ - tmpl % (idx, cls._meta.db_table, pk) - for idx, cls in enumerate(self.item._feincms_content_types) - ]) - sql = 'SELECT * FROM ( ' + sql + ' ) AS ct ORDER BY ct_idx' + sql = " UNION ".join( + [ + tmpl % (idx, cls._meta.db_table, pk) + for idx, cls in enumerate(self.item._feincms_content_types) + ] + ) + sql = "SELECT * FROM ( " + sql + " ) AS ct ORDER BY ct_idx" cursor = connections[self.db].cursor() cursor.execute(sql, args) @@ -200,55 +195,56 @@ def _populate_content_type_caches(self, types): for region, counts in self._fetch_content_type_counts().items(): for pk, ct_idx in counts: counts_by_type.setdefault( - self.item._feincms_content_types[ct_idx], - [], + self.item._feincms_content_types[ct_idx], [] ).append((region, pk)) # Resolve abstract to concrete content types content_types = ( - cls for cls in self.item._feincms_content_types + cls + for cls in self.item._feincms_content_types if issubclass(cls, tuple(types)) ) for cls in content_types: counts = counts_by_type.get(cls) - if cls not in self._cache['cts']: + if cls not in self._cache["cts"]: if counts: - self._cache['cts'][cls] = list(cls.get_queryset(reduce( - operator.or_, - (Q(region=r[0], parent=r[1]) for r in counts) - ))) + self._cache["cts"][cls] = list( + cls.get_queryset( + reduce( + operator.or_, + (Q(region=r[0], parent=r[1]) for r in counts), + ) + ) + ) else: - self._cache['cts'][cls] = [] + self._cache["cts"][cls] = [] # share this content proxy object between all content items # so that each can use obj.parent.content to determine its # relationship to its siblings, etc. - for cls, objects in self._cache['cts'].items(): + for cls, objects in self._cache["cts"].items(): for obj in objects: - setattr(obj.parent, '_content_proxy', self) + setattr(obj.parent, "_content_proxy", self) def _fetch_regions(self): """ Fetches all content types and group content types into regions """ - if 'regions' not in self._cache: - self._populate_content_type_caches( - self.item._feincms_content_types) + if "regions" not in self._cache: + self._populate_content_type_caches(self.item._feincms_content_types) contents = {} - for cls, content_list in self._cache['cts'].items(): + for cls, content_list in self._cache["cts"].items(): for instance in content_list: contents.setdefault(instance.region, []).append(instance) - self._cache['regions'] = dict( - ( - region, - sorted(instances, key=lambda c: c.ordering), - ) for region, instances in contents.items() + self._cache["regions"] = dict( + (region, sorted(instances, key=lambda c: c.ordering)) + for region, instances in contents.items() ) - return self._cache['regions'] + return self._cache["regions"] def all_of_type(self, type_or_tuple): """ @@ -262,11 +258,11 @@ def all_of_type(self, type_or_tuple): """ content_list = [] - if not hasattr(type_or_tuple, '__iter__'): + if not hasattr(type_or_tuple, "__iter__"): type_or_tuple = (type_or_tuple,) self._populate_content_type_caches(type_or_tuple) - for type, contents in self._cache['cts'].items(): + for type, contents in self._cache["cts"].items(): if any(issubclass(type, t) for t in type_or_tuple): content_list.extend(contents) @@ -277,16 +273,17 @@ def _get_media(self): Collect the media files of all content types of the current object """ - if 'media' not in self._cache: + if "media" not in self._cache: media = Media() for contents in self._fetch_regions().values(): for content in contents: - if hasattr(content, 'media'): + if hasattr(content, "media"): media = media + content.media - self._cache['media'] = media - return self._cache['media'] + self._cache["media"] = media + return self._cache["media"] + media = property(_get_media) def __getattr__(self, attr): @@ -297,7 +294,7 @@ def __getattr__(self, attr): has the inherited flag set, this method will go up the ancestor chain until either some item contents have found or no ancestors are left. """ - if (attr.startswith('__')): + if attr.startswith("__"): raise AttributeError # Do not trigger loading of real content type models if not necessary @@ -337,15 +334,15 @@ def register_regions(cls, *regions): ) """ - if hasattr(cls, 'template'): + if hasattr(cls, "template"): warnings.warn( - 'Ignoring second call to register_regions.', - RuntimeWarning) + "Ignoring second call to register_regions.", RuntimeWarning + ) return # implicitly creates a dummy template object -- the item editor # depends on the presence of a template. - cls.template = Template('', '', regions) + cls.template = Template("", "", regions) cls._feincms_all_regions = cls.template.regions @classmethod @@ -374,7 +371,7 @@ def register_templates(cls, *templates): }) """ - if not hasattr(cls, '_feincms_templates'): + if not hasattr(cls, "_feincms_templates"): cls._feincms_templates = OrderedDict() cls.TEMPLATES_CHOICES = [] @@ -387,21 +384,33 @@ def register_templates(cls, *templates): instances[template.key] = template try: - field = next(iter( - field for field in cls._meta.local_fields - if field.name == 'template_key')) + field = next( + iter( + field + for field in cls._meta.local_fields + if field.name == "template_key" + ) + ) except (StopIteration,): cls.add_to_class( - 'template_key', - models.CharField(_('template'), max_length=255, choices=( - # Dummy choice to trick Django. Cannot be empty, - # otherwise admin.E023 happens. - ('__dummy', '__dummy'), - )) + "template_key", + models.CharField( + _("template"), + max_length=255, + choices=( + # Dummy choice to trick Django. Cannot be empty, + # otherwise admin.E023 happens. + ("__dummy", "__dummy"), + ), + ), + ) + field = next( + iter( + field + for field in cls._meta.local_fields + if field.name == "template_key" + ) ) - field = next(iter( - field for field in cls._meta.local_fields - if field.name == 'template_key')) def _template(self): ensure_completely_loaded() @@ -412,12 +421,13 @@ def _template(self): # return first template as a fallback if the template # has changed in-between return self._feincms_templates[ - list(self._feincms_templates.keys())[0]] + list(self._feincms_templates.keys())[0] + ] cls.template = property(_template) cls.TEMPLATE_CHOICES = [ - (template_.key, template_.title,) + (template_.key, template_.title) for template_ in cls._feincms_templates.values() ] try: @@ -445,7 +455,7 @@ def content(self): ``content_proxy_class`` member variable. """ - if not hasattr(self, '_content_proxy'): + if not hasattr(self, "_content_proxy"): self._content_proxy = self.content_proxy_class(self) return self._content_proxy @@ -472,19 +482,20 @@ def _create_content_base(cls): class Meta: abstract = True app_label = cls._meta.app_label - ordering = ['ordering'] + ordering = ["ordering"] def __str__(self): return ( - '%s, region=%s,' - ' ordering=%d>') % ( + "%s, region=%s," " ordering=%d>" + ) % ( self.__class__.__name__, self.pk, self.parent.__class__.__name__, self.parent.pk, self.parent, self.region, - self.ordering) + self.ordering, + ) def render(self, **kwargs): """ @@ -495,7 +506,7 @@ def render(self, **kwargs): time instead of adding region-specific render methods. """ - render_fn = getattr(self, 'render_%s' % self.region, None) + render_fn = getattr(self, "render_%s" % self.region, None) if render_fn: return render_fn(**kwargs) @@ -512,31 +523,33 @@ def get_queryset(cls, filter_args): # needs to know where a model comes # from, therefore we ensure that the # module is always known. - '__module__': cls.__module__, - '__str__': __str__, - 'render': render, - 'get_queryset': classmethod(get_queryset), - 'Meta': Meta, - 'parent': models.ForeignKey( - cls, related_name='%(class)s_set', - on_delete=models.CASCADE), - 'region': models.CharField(max_length=255), - 'ordering': models.IntegerField(_('ordering'), default=0), + "__module__": cls.__module__, + "__str__": __str__, + "render": render, + "get_queryset": classmethod(get_queryset), + "Meta": Meta, + "parent": models.ForeignKey( + cls, related_name="%(class)s_set", on_delete=models.CASCADE + ), + "region": models.CharField(max_length=255), + "ordering": models.IntegerField(_("ordering"), default=0), } # create content base type and save reference on CMS class - name = '_Internal%sContentTypeBase' % cls.__name__ + name = "_Internal%sContentTypeBase" % cls.__name__ if hasattr(sys.modules[cls.__module__], name): warnings.warn( - 'The class %s.%s has the same name as the class that ' - 'FeinCMS auto-generates based on %s.%s. To avoid database' - 'errors and import clashes, rename one of these classes.' + "The class %s.%s has the same name as the class that " + "FeinCMS auto-generates based on %s.%s. To avoid database" + "errors and import clashes, rename one of these classes." % (cls.__module__, name, cls.__module__, cls.__name__), - RuntimeWarning) + RuntimeWarning, + ) cls._feincms_content_model = python_2_unicode_compatible( - type(str(name), (models.Model,), attrs)) + type(str(name), (models.Model,), attrs) + ) # list of concrete content types cls._feincms_content_types = [] @@ -557,23 +570,24 @@ def get_queryset(cls, filter_args): # list of item editor context processors, will be extended by # content types - if hasattr(cls, 'feincms_item_editor_context_processors'): + if hasattr(cls, "feincms_item_editor_context_processors"): cls.feincms_item_editor_context_processors = list( - cls.feincms_item_editor_context_processors) + cls.feincms_item_editor_context_processors + ) else: cls.feincms_item_editor_context_processors = [] # list of templates which should be included in the item editor, # will be extended by content types - if hasattr(cls, 'feincms_item_editor_includes'): + if hasattr(cls, "feincms_item_editor_includes"): cls.feincms_item_editor_includes = dict( - cls.feincms_item_editor_includes) + cls.feincms_item_editor_includes + ) else: cls.feincms_item_editor_includes = {} @classmethod - def create_content_type(cls, model, regions=None, class_name=None, - **kwargs): + def create_content_type(cls, model, regions=None, class_name=None, **kwargs): """ This is the method you'll use to create concrete content types. @@ -622,14 +636,19 @@ def create_content_type(cls, model, regions=None, class_name=None, # content types with the same class name because of related_name # clashes try: - getattr(cls, '%s_set' % class_name.lower()) + getattr(cls, "%s_set" % class_name.lower()) warnings.warn( - 'Cannot create content type using %s.%s for %s.%s,' - ' because %s_set is already taken.' % ( - model.__module__, class_name, - cls.__module__, cls.__name__, - class_name.lower()), - RuntimeWarning) + "Cannot create content type using %s.%s for %s.%s," + " because %s_set is already taken." + % ( + model.__module__, + class_name, + cls.__module__, + cls.__name__, + class_name.lower(), + ), + RuntimeWarning, + ) return except AttributeError: # everything ok @@ -637,16 +656,16 @@ def create_content_type(cls, model, regions=None, class_name=None, if not model._meta.abstract: raise ImproperlyConfigured( - 'Cannot create content type from' - ' non-abstract model (yet).') + "Cannot create content type from" " non-abstract model (yet)." + ) - if not hasattr(cls, '_feincms_content_model'): + if not hasattr(cls, "_feincms_content_model"): cls._create_content_base() feincms_content_base = cls._feincms_content_model class Meta(feincms_content_base.Meta): - db_table = '%s_%s' % (cls._meta.db_table, class_name.lower()) + db_table = "%s_%s" % (cls._meta.db_table, class_name.lower()) verbose_name = model._meta.verbose_name verbose_name_plural = model._meta.verbose_name_plural permissions = model._meta.permissions @@ -659,26 +678,21 @@ class Meta(feincms_content_base.Meta): # content type may be used by several CMS # base models at the same time (f.e. in # the blog and the page module). - '__module__': cls.__module__, - 'Meta': Meta, + "__module__": cls.__module__, + "Meta": Meta, } - new_type = type( - str(class_name), - (model, feincms_content_base,), - attrs, - ) + new_type = type(str(class_name), (model, feincms_content_base), attrs) cls._feincms_content_types.append(new_type) - if hasattr(getattr(new_type, 'process', None), '__call__'): + if hasattr(getattr(new_type, "process", None), "__call__"): cls._feincms_content_types_with_process.append(new_type) - if hasattr(getattr(new_type, 'finalize', None), '__call__'): + if hasattr(getattr(new_type, "finalize", None), "__call__"): cls._feincms_content_types_with_finalize.append(new_type) # content types can be limited to a subset of regions if not regions: - regions = set([ - region.key for region in cls._feincms_all_regions]) + regions = set([region.key for region in cls._feincms_all_regions]) for region in cls._feincms_all_regions: if region.key in regions: @@ -689,7 +703,7 @@ class Meta(feincms_content_base.Meta): # f.e. for the update_rsscontent management command, which needs to # find all concrete RSSContent types, so that the RSS feeds can be # fetched - if not hasattr(model, '_feincms_content_models'): + if not hasattr(model, "_feincms_content_models"): model._feincms_content_models = [] model._feincms_content_models.append(new_type) @@ -699,36 +713,38 @@ class Meta(feincms_content_base.Meta): # Handle optgroup argument for grouping content types in the item # editor - optgroup = kwargs.pop('optgroup', None) + optgroup = kwargs.pop("optgroup", None) if optgroup: new_type.optgroup = optgroup # customization hook. - if hasattr(new_type, 'initialize_type'): + if hasattr(new_type, "initialize_type"): new_type.initialize_type(**kwargs) else: for k, v in kwargs.items(): setattr(new_type, k, v) # collect item editor context processors from the content type - if hasattr(model, 'feincms_item_editor_context_processors'): + if hasattr(model, "feincms_item_editor_context_processors"): cls.feincms_item_editor_context_processors.extend( - model.feincms_item_editor_context_processors) + model.feincms_item_editor_context_processors + ) # collect item editor includes from the content type - if hasattr(model, 'feincms_item_editor_includes'): + if hasattr(model, "feincms_item_editor_includes"): for key, incls in model.feincms_item_editor_includes.items(): - cls.feincms_item_editor_includes.setdefault( - key, set()).update(incls) + cls.feincms_item_editor_includes.setdefault(key, set()).update( + incls + ) ensure_completely_loaded(force=True) return new_type @property def _django_content_type(self): - if not getattr(self, '_cached_django_content_type', None): - self.__class__._cached_django_content_type = ( - ContentType.objects.get_for_model(self)) + if not getattr(self, "_cached_django_content_type", None): + ct = ContentType.objects.get_for_model(self) + self.__class__._cached_django_content_type = ct return self.__class__._cached_django_content_type @classmethod @@ -740,8 +756,10 @@ def content_type_for(cls, model): concrete_type = Page.content_type_for(VideoContent) """ - if (not hasattr(cls, '_feincms_content_types') or - not cls._feincms_content_types): + if ( + not hasattr(cls, "_feincms_content_types") + or not cls._feincms_content_types + ): return None for type in cls._feincms_content_types: @@ -756,10 +774,11 @@ def _needs_templates(cls): # helper which can be used to ensure that either register_regions # or register_templates has been executed before proceeding - if not hasattr(cls, 'template'): + if not hasattr(cls, "template"): raise ImproperlyConfigured( - 'You need to register at least one' - ' template or one region on %s.' % cls.__name__) + "You need to register at least one" + " template or one region on %s." % cls.__name__ + ) @classmethod def _needs_content_types(cls): @@ -767,10 +786,11 @@ def _needs_content_types(cls): # Check whether any content types have been created for this base # class - if not getattr(cls, '_feincms_content_types', None): + if not getattr(cls, "_feincms_content_types", None): raise ImproperlyConfigured( - 'You need to create at least one' - ' content type for the %s model.' % cls.__name__) + "You need to create at least one" + " content type for the %s model." % cls.__name__ + ) def copy_content_from(self, obj): """ @@ -781,8 +801,7 @@ def copy_content_from(self, obj): for cls in self._feincms_content_types: for content in cls.objects.filter(parent=obj): - new = copy_model_instance( - content, exclude=('id', 'parent')) + new = copy_model_instance(content, exclude=("id", "parent")) new.parent = self new.save() @@ -810,7 +829,7 @@ def register_with_reversion(cls): follow = [] for content_type in cls._feincms_content_types: - follow.append('%s_set' % content_type.__name__.lower()) + follow.append("%s_set" % content_type.__name__.lower()) register(content_type) register(cls, follow=follow) diff --git a/feincms/module/extensions/changedate.py b/feincms/module/extensions/changedate.py index dd543dbbc..fdfd93475 100644 --- a/feincms/module/extensions/changedate.py +++ b/feincms/module/extensions/changedate.py @@ -6,6 +6,8 @@ from feincms.extensions.changedate import * warnings.warn( - 'Import %(name)s from feincms.extensions.%(name)s' % { - 'name': __name__.split('.')[-1], - }, DeprecationWarning, stacklevel=2) + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/ct_tracker.py b/feincms/module/extensions/ct_tracker.py index 7b94fd55f..f810b346e 100644 --- a/feincms/module/extensions/ct_tracker.py +++ b/feincms/module/extensions/ct_tracker.py @@ -6,6 +6,8 @@ from feincms.extensions.ct_tracker import * warnings.warn( - 'Import %(name)s from feincms.extensions.%(name)s' % { - 'name': __name__.split('.')[-1], - }, DeprecationWarning, stacklevel=2) + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/datepublisher.py b/feincms/module/extensions/datepublisher.py index 44fed5edb..313568d74 100644 --- a/feincms/module/extensions/datepublisher.py +++ b/feincms/module/extensions/datepublisher.py @@ -6,6 +6,8 @@ from feincms.extensions.datepublisher import * warnings.warn( - 'Import %(name)s from feincms.extensions.%(name)s' % { - 'name': __name__.split('.')[-1], - }, DeprecationWarning, stacklevel=2) + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/featured.py b/feincms/module/extensions/featured.py index 05b59a87a..7713d026b 100644 --- a/feincms/module/extensions/featured.py +++ b/feincms/module/extensions/featured.py @@ -6,6 +6,8 @@ from feincms.extensions.featured import * warnings.warn( - 'Import %(name)s from feincms.extensions.%(name)s' % { - 'name': __name__.split('.')[-1], - }, DeprecationWarning, stacklevel=2) + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/seo.py b/feincms/module/extensions/seo.py index 8dc6add93..aa386950f 100644 --- a/feincms/module/extensions/seo.py +++ b/feincms/module/extensions/seo.py @@ -6,6 +6,8 @@ from feincms.extensions.seo import * warnings.warn( - 'Import %(name)s from feincms.extensions.%(name)s' % { - 'name': __name__.split('.')[-1], - }, DeprecationWarning, stacklevel=2) + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index 7d8842186..c89c8d538 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -6,6 +6,8 @@ from feincms.extensions.translations import * warnings.warn( - 'Import %(name)s from feincms.extensions.%(name)s' % { - 'name': __name__.split('.')[-1], - }, DeprecationWarning, stacklevel=2) + "Import %(name)s from feincms.extensions.%(name)s" + % {"name": __name__.split(".")[-1]}, + DeprecationWarning, + stacklevel=2, +) diff --git a/feincms/module/medialibrary/__init__.py b/feincms/module/medialibrary/__init__.py index fc7e02a00..98a59597d 100644 --- a/feincms/module/medialibrary/__init__.py +++ b/feincms/module/medialibrary/__init__.py @@ -7,6 +7,6 @@ import logging # ------------------------------------------------------------------------ -logger = logging.getLogger('feincms.medialibrary') +logger = logging.getLogger("feincms.medialibrary") # ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/contents.py b/feincms/module/medialibrary/contents.py index b5b2cd435..ada88df68 100644 --- a/feincms/module/medialibrary/contents.py +++ b/feincms/module/medialibrary/contents.py @@ -11,8 +11,8 @@ class MediaFileContentInline(FeinCMSInline): - raw_id_fields = ('mediafile',) - radio_fields = {'type': admin.VERTICAL} + raw_id_fields = ("mediafile",) + radio_fields = {"type": admin.VERTICAL} class MediaFileContent(ContentWithMediaFile): @@ -44,36 +44,35 @@ class MediaFileContent(ContentWithMediaFile): class Meta: abstract = True - verbose_name = _('media file') - verbose_name_plural = _('media files') + verbose_name = _("media file") + verbose_name_plural = _("media files") @classmethod def initialize_type(cls, TYPE_CHOICES=None): if TYPE_CHOICES is None: raise ImproperlyConfigured( - 'You have to set TYPE_CHOICES when' - ' creating a %s' % cls.__name__) + "You have to set TYPE_CHOICES when" " creating a %s" % cls.__name__ + ) cls.add_to_class( - 'type', + "type", models.CharField( - _('type'), + _("type"), max_length=20, choices=TYPE_CHOICES, default=TYPE_CHOICES[0][0], - ) + ), ) def render(self, **kwargs): return ct_render_to_string( [ - 'content/mediafile/%s_%s.html' % ( - self.mediafile.type, self.type), - 'content/mediafile/%s.html' % self.mediafile.type, - 'content/mediafile/%s.html' % self.type, - 'content/mediafile/default.html', + "content/mediafile/%s_%s.html" % (self.mediafile.type, self.type), + "content/mediafile/%s.html" % self.mediafile.type, + "content/mediafile/%s.html" % self.type, + "content/mediafile/default.html", ], - {'content': self}, - request=kwargs.get('request'), - context=kwargs.get('context'), + {"content": self}, + request=kwargs.get("request"), + context=kwargs.get("context"), ) diff --git a/feincms/module/medialibrary/fields.py b/feincms/module/medialibrary/fields.py index 95046060b..c27f8fb41 100644 --- a/feincms/module/medialibrary/fields.py +++ b/feincms/module/medialibrary/fields.py @@ -18,7 +18,7 @@ from .thumbnail import admin_thumbnail -__all__ = ('MediaFileForeignKey', 'ContentWithMediaFile') +__all__ = ("MediaFileForeignKey", "ContentWithMediaFile") # ------------------------------------------------------------------------ @@ -29,20 +29,21 @@ def __init__(self, original): def label_for_value(self, value): key = self.rel.get_related_field().name try: - obj = self.rel.to._default_manager.using(self.db).get( - **{key: value}) - label = [' %s' % escape( - shorten_string(six.text_type(obj)))] + obj = self.rel.to._default_manager.using(self.db).get(**{key: value}) + label = [ + " %s" % escape(shorten_string(six.text_type(obj))) + ] image = admin_thumbnail(obj) if image: label.append( '
' % image) + "/>" % image + ) - return ''.join(label) + return "".join(label) except (ValueError, self.rel.to.DoesNotExist): - return '' + return "" class MediaFileForeignKey(models.ForeignKey): @@ -53,24 +54,25 @@ class MediaFileForeignKey(models.ForeignKey): """ def __init__(self, *args, **kwargs): - if not args and 'to' not in kwargs: + if not args and "to" not in kwargs: args = (MediaFile,) super(MediaFileForeignKey, self).__init__(*args, **kwargs) def formfield(self, **kwargs): - if 'widget' in kwargs and isinstance( - kwargs['widget'], ForeignKeyRawIdWidget): - kwargs['widget'] = MediaFileForeignKeyRawIdWidget(kwargs['widget']) + if "widget" in kwargs and isinstance(kwargs["widget"], ForeignKeyRawIdWidget): + kwargs["widget"] = MediaFileForeignKeyRawIdWidget(kwargs["widget"]) return super(MediaFileForeignKey, self).formfield(**kwargs) class ContentWithMediaFile(models.Model): class feincms_item_editor_inline(FeinCMSInline): - raw_id_fields = ('mediafile',) + raw_id_fields = ("mediafile",) mediafile = MediaFileForeignKey( - MediaFile, verbose_name=_('media file'), related_name='+', - on_delete=models.PROTECT + MediaFile, + verbose_name=_("media file"), + related_name="+", + on_delete=models.PROTECT, ) class Meta: @@ -83,16 +85,22 @@ class AdminFileWithPreviewWidget(AdminFileWidget): Simple AdminFileWidget, but detects if the file is an image and tries to render a small thumbnail besides the input field. """ + def render(self, name, value, attrs=None, *args, **kwargs): r = super(AdminFileWithPreviewWidget, self).render( - name, value, attrs=attrs, *args, **kwargs) + name, value, attrs=attrs, *args, **kwargs + ) - if value and getattr(value, 'instance', None): + if value and getattr(value, "instance", None): image = admin_thumbnail(value.instance) if image: - r = mark_safe(( - '' % image) + r) + r = mark_safe( + ( + '" % image + ) + + r + ) return r diff --git a/feincms/module/medialibrary/forms.py b/feincms/module/medialibrary/forms.py index e592bd591..c43e91cc3 100644 --- a/feincms/module/medialibrary/forms.py +++ b/feincms/module/medialibrary/forms.py @@ -20,41 +20,43 @@ class MediaCategoryAdminForm(forms.ModelForm): class Meta: model = Category - fields = '__all__' + fields = "__all__" def clean_parent(self): - data = self.cleaned_data['parent'] + data = self.cleaned_data["parent"] if data is not None and self.instance in data.path_list(): - raise forms.ValidationError( - _("This would create a loop in the hierarchy")) + raise forms.ValidationError(_("This would create a loop in the hierarchy")) return data def __init__(self, *args, **kwargs): super(MediaCategoryAdminForm, self).__init__(*args, **kwargs) - self.fields['parent'].queryset =\ - self.fields['parent'].queryset.exclude(pk=self.instance.pk) + self.fields["parent"].queryset = self.fields["parent"].queryset.exclude( + pk=self.instance.pk + ) # ------------------------------------------------------------------------ class MediaFileAdminForm(forms.ModelForm): class Meta: model = MediaFile - widgets = {'file': AdminFileWithPreviewWidget} - fields = '__all__' + widgets = {"file": AdminFileWithPreviewWidget} + fields = "__all__" def __init__(self, *args, **kwargs): super(MediaFileAdminForm, self).__init__(*args, **kwargs) if settings.FEINCMS_MEDIAFILE_OVERWRITE and self.instance.id: field = self.instance.file.field - if not hasattr(field, '_feincms_generate_filename_patched'): + if not hasattr(field, "_feincms_generate_filename_patched"): original_generate = field.generate_filename def _gen_fname(instance, filename): - if instance.id and hasattr(instance, 'original_name'): - logger.info("Overwriting file %s with new data" % ( - instance.original_name)) + if instance.id and hasattr(instance, "original_name"): + logger.info( + "Overwriting file %s with new data" + % (instance.original_name) + ) instance.file.storage.delete(instance.original_name) return instance.original_name @@ -65,18 +67,21 @@ def _gen_fname(instance, filename): def clean_file(self): if settings.FEINCMS_MEDIAFILE_OVERWRITE and self.instance.id: - new_base, new_ext = os.path.splitext( - self.cleaned_data['file'].name) + new_base, new_ext = os.path.splitext(self.cleaned_data["file"].name) old_base, old_ext = os.path.splitext(self.instance.file.name) if new_ext.lower() != old_ext.lower(): - raise forms.ValidationError(_( - "Cannot overwrite with different file type (attempt to" - " overwrite a %(old_ext)s with a %(new_ext)s)" - ) % {'old_ext': old_ext, 'new_ext': new_ext}) + raise forms.ValidationError( + _( + "Cannot overwrite with different file type (attempt to" + " overwrite a %(old_ext)s with a %(new_ext)s)" + ) + % {"old_ext": old_ext, "new_ext": new_ext} + ) self.instance.original_name = self.instance.file.name - return self.cleaned_data['file'] + return self.cleaned_data["file"] + # ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index d52e9fb2c..ae20ae55b 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -19,6 +19,7 @@ from django.utils.safestring import mark_safe from django.utils.translation import ungettext, ugettext_lazy as _ from django.views.decorators.csrf import csrf_protect + try: from django.urls import reverse except ImportError: @@ -37,11 +38,11 @@ # ----------------------------------------------------------------------- class CategoryAdmin(admin.ModelAdmin): form = MediaCategoryAdminForm - list_display = ['path'] - list_filter = ['parent'] + list_display = ["path"] + list_filter = ["parent"] list_per_page = 25 - search_fields = ['title'] - prepopulated_fields = {'slug': ('title',)} + search_fields = ["title"] + prepopulated_fields = {"slug": ("title",)} # ------------------------------------------------------------------------ @@ -51,10 +52,10 @@ class AddCategoryForm(forms.Form): category = forms.ModelChoiceField(Category.objects.all()) form = None - if 'apply' in request.POST: + if "apply" in request.POST: form = AddCategoryForm(request.POST) if form.is_valid(): - category = form.cleaned_data['category'] + category = form.cleaned_data["category"] count = 0 for mediafile in queryset: @@ -62,28 +63,30 @@ class AddCategoryForm(forms.Form): count += 1 message = ungettext( - 'Successfully added %(count)d media file to %(category)s.', - 'Successfully added %(count)d media files to %(category)s.', - count) % {'count': count, 'category': category} + "Successfully added %(count)d media file to %(category)s.", + "Successfully added %(count)d media files to %(category)s.", + count, + ) % {"count": count, "category": category} modeladmin.message_user(request, message) return HttpResponseRedirect(request.get_full_path()) - if 'cancel' in request.POST: + if "cancel" in request.POST: return HttpResponseRedirect(request.get_full_path()) if not form: - form = AddCategoryForm(initial={ - '_selected_action': request.POST.getlist( - admin.ACTION_CHECKBOX_NAME), - }) + form = AddCategoryForm( + initial={ + "_selected_action": request.POST.getlist(admin.ACTION_CHECKBOX_NAME) + } + ) - return render(request, 'admin/medialibrary/add_to_category.html', { - 'mediafiles': queryset, - 'category_form': form, - 'opts': modeladmin.model._meta, - }) + return render( + request, + "admin/medialibrary/add_to_category.html", + {"mediafiles": queryset, "category_form": form, "opts": modeladmin.model._meta}, + ) -assign_category.short_description = _('Add selected media files to category') +assign_category.short_description = _("Add selected media files to category") # ------------------------------------------------------------------------- @@ -98,12 +101,10 @@ def save_as_zipfile(modeladmin, request, queryset): messages.error(request, _("ZIP file export failed: %s") % str(e)) return - return HttpResponseRedirect( - os.path.join(django_settings.MEDIA_URL, zip_name)) + return HttpResponseRedirect(os.path.join(django_settings.MEDIA_URL, zip_name)) -save_as_zipfile.short_description = _( - 'Export selected media files as zip file') +save_as_zipfile.short_description = _("Export selected media files as zip file") # ------------------------------------------------------------------------ @@ -111,14 +112,13 @@ class MediaFileAdmin(ExtensionModelAdmin): form = MediaFileAdminForm save_on_top = True - date_hierarchy = 'created' + date_hierarchy = "created" inlines = [admin_translationinline(MediaFileTranslation)] - list_display = [ - 'admin_thumbnail', '__str__', 'file_info', 'formatted_created'] - list_display_links = ['__str__'] - list_filter = ['type', 'categories'] + list_display = ["admin_thumbnail", "__str__", "file_info", "formatted_created"] + list_display_links = ["__str__"] + list_filter = ["type", "categories"] list_per_page = 25 - search_fields = ['copyright', 'file', 'translations__caption'] + search_fields = ["copyright", "file", "translations__caption"] filter_horizontal = ("categories",) actions = [assign_category, save_as_zipfile] @@ -127,46 +127,50 @@ def get_urls(self): return [ url( - r'^mediafile-bulk-upload/$', + r"^mediafile-bulk-upload/$", self.admin_site.admin_view(MediaFileAdmin.bulk_upload), {}, - name='mediafile_bulk_upload', - ), + name="mediafile_bulk_upload", + ) ] + super(MediaFileAdmin, self).get_urls() def changelist_view(self, request, extra_context=None): if extra_context is None: extra_context = {} - extra_context['categories'] = Category.objects.order_by('title') + extra_context["categories"] = Category.objects.order_by("title") return super(MediaFileAdmin, self).changelist_view( - request, extra_context=extra_context) + request, extra_context=extra_context + ) def admin_thumbnail(self, obj): image = admin_thumbnail(obj) if image: - return mark_safe(""" + return mark_safe( + """ - """ % { - 'url': obj.file.url, - 'image': image} + """ + % {"url": obj.file.url, "image": image} ) - return '' - admin_thumbnail.short_description = _('Preview') + return "" + + admin_thumbnail.short_description = _("Preview") def formatted_file_size(self, obj): return filesizeformat(obj.file_size) + formatted_file_size.short_description = _("file size") - formatted_file_size.admin_order_field = 'file_size' + formatted_file_size.admin_order_field = "file_size" def formatted_created(self, obj): return obj.created.strftime("%Y-%m-%d") + formatted_created.short_description = _("created") - formatted_created.admin_order_field = 'created' + formatted_created.admin_order_field = "created" def file_type(self, obj): t = obj.filetypes_dict[obj.type] - if obj.type == 'image': + if obj.type == "image": # get_image_dimensions is expensive / slow if the storage is not # local filesystem (indicated by availability the path property) try: @@ -180,8 +184,9 @@ def file_type(self, obj): except (IOError, TypeError, ValueError) as e: t += " (%s)" % e return mark_safe(t) - file_type.admin_order_field = 'type' - file_type.short_description = _('file type') + + file_type.admin_order_field = "type" + file_type.short_description = _("file type") def file_info(self, obj): """ @@ -191,47 +196,54 @@ def file_info(self, obj): the file name later on, this can be used to access the file name from JS, like for example a TinyMCE connector shim. """ - return mark_safe(( - '' - ' %s
%s, %s' - ) % ( - obj.id, - obj.file.name, - obj.id, - shorten_string(os.path.basename(obj.file.name), max_length=40), - self.file_type(obj), - self.formatted_file_size(obj), - )) - file_info.admin_order_field = 'file' - file_info.short_description = _('file info') + return mark_safe( + ( + '' + " %s
%s, %s" + ) + % ( + obj.id, + obj.file.name, + obj.id, + shorten_string(os.path.basename(obj.file.name), max_length=40), + self.file_type(obj), + self.formatted_file_size(obj), + ) + ) + + file_info.admin_order_field = "file" + file_info.short_description = _("file info") @staticmethod @csrf_protect - @permission_required('medialibrary.add_mediafile') + @permission_required("medialibrary.add_mediafile") def bulk_upload(request): - if request.method == 'POST' and 'data' in request.FILES: + if request.method == "POST" and "data" in request.FILES: try: count = import_zipfile( - request.POST.get('category'), - request.POST.get('overwrite', False), - request.FILES['data']) + request.POST.get("category"), + request.POST.get("overwrite", False), + request.FILES["data"], + ) messages.info(request, _("%d files imported") % count) except Exception as e: messages.error(request, _("ZIP import failed: %s") % e) else: messages.error(request, _("No input file given")) - return HttpResponseRedirect( - reverse('admin:medialibrary_mediafile_changelist')) + return HttpResponseRedirect(reverse("admin:medialibrary_mediafile_changelist")) def get_queryset(self, request): - return super(MediaFileAdmin, self).get_queryset(request).transform( - lookup_translations()) + return ( + super(MediaFileAdmin, self) + .get_queryset(request) + .transform(lookup_translations()) + ) def save_model(self, request, obj, form, change): obj.purge_translation_cache() - return super(MediaFileAdmin, self).save_model( - request, obj, form, change) + return super(MediaFileAdmin, self).save_model(request, obj, form, change) + # ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/models.py b/feincms/module/medialibrary/models.py index 66fd1d069..424abd327 100644 --- a/feincms/module/medialibrary/models.py +++ b/feincms/module/medialibrary/models.py @@ -18,7 +18,10 @@ from feincms import settings from feincms.models import ExtensionsMixin from feincms.translations import ( - TranslatedObjectMixin, Translation, TranslatedObjectManager) + TranslatedObjectMixin, + Translation, + TranslatedObjectManager, +) from . import logger @@ -29,9 +32,9 @@ class CategoryManager(models.Manager): Simple manager which exists only to supply ``.select_related("parent")`` on querysets since we can't even __str__ efficiently without it. """ + def get_queryset(self): - return super(CategoryManager, self).get_queryset().select_related( - "parent") + return super(CategoryManager, self).get_queryset().select_related("parent") # ------------------------------------------------------------------------ @@ -42,26 +45,30 @@ class Category(models.Model): library. """ - title = models.CharField(_('title'), max_length=200) + title = models.CharField(_("title"), max_length=200) parent = models.ForeignKey( - 'self', blank=True, null=True, + "self", + blank=True, + null=True, on_delete=models.CASCADE, - related_name='children', limit_choices_to={'parent__isnull': True}, - verbose_name=_('parent')) + related_name="children", + limit_choices_to={"parent__isnull": True}, + verbose_name=_("parent"), + ) - slug = models.SlugField(_('slug'), max_length=150) + slug = models.SlugField(_("slug"), max_length=150) class Meta: - ordering = ['parent__title', 'title'] - verbose_name = _('category') - verbose_name_plural = _('categories') - app_label = 'medialibrary' + ordering = ["parent__title", "title"] + verbose_name = _("category") + verbose_name_plural = _("categories") + app_label = "medialibrary" objects = CategoryManager() def __str__(self): if self.parent_id: - return '%s - %s' % (self.parent.title, self.title) + return "%s - %s" % (self.parent.title, self.title) return self.title @@ -70,6 +77,7 @@ def save(self, *args, **kwargs): self.slug = slugify(self.title) super(Category, self).save(*args, **kwargs) + save.alters_data = True def path_list(self): @@ -80,7 +88,7 @@ def path_list(self): return p def path(self): - return ' - '.join((f.title for f in self.path_list())) + return " - ".join((f.title for f in self.path_list())) # ------------------------------------------------------------------------ @@ -93,26 +101,25 @@ class MediaFileBase(models.Model, ExtensionsMixin, TranslatedObjectMixin): """ file = models.FileField( - _('file'), max_length=255, - upload_to=settings.FEINCMS_MEDIALIBRARY_UPLOAD_TO) - type = models.CharField( - _('file type'), max_length=12, editable=False, - choices=()) - created = models.DateTimeField( - _('created'), editable=False, default=timezone.now) - copyright = models.CharField(_('copyright'), max_length=200, blank=True) + _("file"), max_length=255, upload_to=settings.FEINCMS_MEDIALIBRARY_UPLOAD_TO + ) + type = models.CharField(_("file type"), max_length=12, editable=False, choices=()) + created = models.DateTimeField(_("created"), editable=False, default=timezone.now) + copyright = models.CharField(_("copyright"), max_length=200, blank=True) file_size = models.IntegerField( - _("file size"), blank=True, null=True, editable=False) + _("file size"), blank=True, null=True, editable=False + ) categories = models.ManyToManyField( - Category, verbose_name=_('categories'), blank=True) + Category, verbose_name=_("categories"), blank=True + ) categories.category_filter = True class Meta: abstract = True - ordering = ['-created'] - verbose_name = _('media file') - verbose_name_plural = _('media files') + ordering = ["-created"] + verbose_name = _("media file") + verbose_name_plural = _("media files") objects = TranslatedObjectManager() @@ -121,7 +128,7 @@ class Meta: @classmethod def reconfigure(cls, upload_to=None, storage=None): - f = cls._meta.get_field('file') + f = cls._meta.get_field("file") # Ugh. Copied relevant parts from django/db/models/fields/files.py # FileField.__init__ (around line 225) if storage: @@ -136,7 +143,7 @@ def register_filetypes(cls, *types): cls.filetypes[0:0] = types choices = [t[0:2] for t in cls.filetypes] cls.filetypes_dict = dict(choices) - cls._meta.get_field('type').choices[:] = choices + cls._meta.get_field("type").choices[:] = choices def __init__(self, *args, **kwargs): super(MediaFileBase, self).__init__(*args, **kwargs) @@ -154,7 +161,7 @@ def __str__(self): pass if trans: - trans = '%s' % trans + trans = "%s" % trans if trans.strip(): return trans return os.path.basename(self.file.name) @@ -194,16 +201,19 @@ def save(self, *args, **kwargs): super(MediaFileBase, self).save(*args, **kwargs) - logger.info("Saved mediafile %d (%s, type %s, %d bytes)" % ( - self.id, self.file.name, self.type, self.file_size or 0)) + logger.info( + "Saved mediafile %d (%s, type %s, %d bytes)" + % (self.id, self.file.name, self.type, self.file_size or 0) + ) # User uploaded a new file. Try to get rid of the old file in # storage, to avoid having orphaned files hanging around. - if getattr(self, '_original_file_name', None): + if getattr(self, "_original_file_name", None): if self.file.name != self._original_file_name: self.delete_mediafile(self._original_file_name) self.purge_translation_cache() + save.alters_data = True def delete_mediafile(self, name=None): @@ -218,39 +228,61 @@ def delete_mediafile(self, name=None): # ------------------------------------------------------------------------ MediaFileBase.register_filetypes( # Should we be using imghdr.what instead of extension guessing? - ('image', _('Image'), lambda f: re.compile( - r'\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$', re.IGNORECASE).search(f)), - ('video', _('Video'), lambda f: re.compile( - r'\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv|flv)$', - re.IGNORECASE).search(f)), - ('audio', _('Audio'), lambda f: re.compile( - r'\.(au|mp3|m4a|wma|oga|ram|wav)$', re.IGNORECASE).search(f)), - ('pdf', _('PDF document'), lambda f: f.lower().endswith('.pdf')), - ('swf', _('Flash'), lambda f: f.lower().endswith('.swf')), - ('txt', _('Text'), lambda f: f.lower().endswith('.txt')), - ('rtf', _('Rich Text'), lambda f: f.lower().endswith('.rtf')), - ('zip', _('Zip archive'), lambda f: f.lower().endswith('.zip')), - ('doc', _('Microsoft Word'), lambda f: re.compile( - r'\.docx?$', re.IGNORECASE).search(f)), - ('xls', _('Microsoft Excel'), lambda f: re.compile( - r'\.xlsx?$', re.IGNORECASE).search(f)), - ('ppt', _('Microsoft PowerPoint'), lambda f: re.compile( - r'\.pptx?$', re.IGNORECASE).search(f)), - ('other', _('Binary'), lambda f: True), # Must be last + ( + "image", + _("Image"), + lambda f: re.compile( + r"\.(bmp|jpe?g|jp2|jxr|gif|png|tiff?)$", re.IGNORECASE + ).search(f), + ), + ( + "video", + _("Video"), + lambda f: re.compile( + r"\.(mov|m[14]v|mp4|avi|mpe?g|qt|ogv|wmv|flv)$", re.IGNORECASE + ).search(f), + ), + ( + "audio", + _("Audio"), + lambda f: re.compile(r"\.(au|mp3|m4a|wma|oga|ram|wav)$", re.IGNORECASE).search( + f + ), + ), + ("pdf", _("PDF document"), lambda f: f.lower().endswith(".pdf")), + ("swf", _("Flash"), lambda f: f.lower().endswith(".swf")), + ("txt", _("Text"), lambda f: f.lower().endswith(".txt")), + ("rtf", _("Rich Text"), lambda f: f.lower().endswith(".rtf")), + ("zip", _("Zip archive"), lambda f: f.lower().endswith(".zip")), + ( + "doc", + _("Microsoft Word"), + lambda f: re.compile(r"\.docx?$", re.IGNORECASE).search(f), + ), + ( + "xls", + _("Microsoft Excel"), + lambda f: re.compile(r"\.xlsx?$", re.IGNORECASE).search(f), + ), + ( + "ppt", + _("Microsoft PowerPoint"), + lambda f: re.compile(r"\.pptx?$", re.IGNORECASE).search(f), + ), + ("other", _("Binary"), lambda f: True), # Must be last ) # ------------------------------------------------------------------------ class MediaFile(MediaFileBase): class Meta: - app_label = 'medialibrary' + app_label = "medialibrary" @receiver(post_delete, sender=MediaFile) def _mediafile_post_delete(sender, instance, **kwargs): instance.delete_mediafile() - logger.info("Deleted mediafile %d (%s)" % ( - instance.id, instance.file.name)) + logger.info("Deleted mediafile %d (%s)" % (instance.id, instance.file.name)) # ------------------------------------------------------------------------ @@ -260,14 +292,14 @@ class MediaFileTranslation(Translation(MediaFile)): Translated media file caption and description. """ - caption = models.CharField(_('caption'), max_length=1000) - description = models.TextField(_('description'), blank=True) + caption = models.CharField(_("caption"), max_length=1000) + description = models.TextField(_("description"), blank=True) class Meta: - verbose_name = _('media file translation') - verbose_name_plural = _('media file translations') - unique_together = ('parent', 'language_code') - app_label = 'medialibrary' + verbose_name = _("media file translation") + verbose_name_plural = _("media file translations") + unique_together = ("parent", "language_code") + app_label = "medialibrary" def __str__(self): return self.caption diff --git a/feincms/module/medialibrary/thumbnail.py b/feincms/module/medialibrary/thumbnail.py index a70c2dc27..5dce9cce9 100644 --- a/feincms/module/medialibrary/thumbnail.py +++ b/feincms/module/medialibrary/thumbnail.py @@ -5,8 +5,8 @@ from feincms.utils import get_object -def default_admin_thumbnail(mediafile, dimensions='100x100', **kwargs): - if mediafile.type != 'image': +def default_admin_thumbnail(mediafile, dimensions="100x100", **kwargs): + if mediafile.type != "image": return None return feincms_thumbnail.thumbnail(mediafile.file, dimensions) @@ -15,9 +15,8 @@ def default_admin_thumbnail(mediafile, dimensions='100x100', **kwargs): _cached_thumbnailer = None -def admin_thumbnail(mediafile, dimensions='100x100'): +def admin_thumbnail(mediafile, dimensions="100x100"): global _cached_thumbnailer if not _cached_thumbnailer: - _cached_thumbnailer = get_object( - settings.FEINCMS_MEDIALIBRARY_THUMBNAIL) + _cached_thumbnailer = get_object(settings.FEINCMS_MEDIALIBRARY_THUMBNAIL) return _cached_thumbnailer(mediafile, dimensions=dimensions) diff --git a/feincms/module/medialibrary/zip.py b/feincms/module/medialibrary/zip.py index 557e82566..2e91376f6 100644 --- a/feincms/module/medialibrary/zip.py +++ b/feincms/module/medialibrary/zip.py @@ -23,7 +23,7 @@ # ------------------------------------------------------------------------ -export_magic = 'feincms-export-01' +export_magic = "feincms-export-01" # ------------------------------------------------------------------------ @@ -47,7 +47,7 @@ def import_zipfile(category_id, overwrite, data): info = {} try: info = json.loads(z.comment) - if info['export_magic'] == export_magic: + if info["export_magic"] == export_magic: is_export_file = True except Exception: pass @@ -57,21 +57,21 @@ def import_zipfile(category_id, overwrite, data): category_id_map = {} if is_export_file: for cat in sorted( - info.get('categories', []), - key=lambda k: k.get('level', 999)): + info.get("categories", []), key=lambda k: k.get("level", 999) + ): new_cat, created = Category.objects.get_or_create( - slug=cat['slug'], - title=cat['title']) - category_id_map[cat['id']] = new_cat - if created and cat.get('parent', 0): - parent_cat = category_id_map.get(cat.get('parent', 0), None) + slug=cat["slug"], title=cat["title"] + ) + category_id_map[cat["id"]] = new_cat + if created and cat.get("parent", 0): + parent_cat = category_id_map.get(cat.get("parent", 0), None) if parent_cat: new_cat.parent = parent_cat new_cat.save() count = 0 for zi in z.infolist(): - if not zi.filename.endswith('/'): + if not zi.filename.endswith("/"): bname = os.path.basename(zi.filename) if bname and not bname.startswith(".") and "." in bname: fname, ext = os.path.splitext(bname) @@ -95,36 +95,34 @@ def import_zipfile(category_id, overwrite, data): mf = MediaFile() if overwrite: mf.file.field.upload_to = wanted_dir - mf.copyright = info.get('copyright', '') - mf.file.save( - target_fname, - ContentFile(z.read(zi.filename)), - save=False) + mf.copyright = info.get("copyright", "") + mf.file.save(target_fname, ContentFile(z.read(zi.filename)), save=False) mf.save() found_metadata = False if is_export_file: try: - for tr in info['translations']: + for tr in info["translations"]: found_metadata = True - mt, mt_created =\ - MediaFileTranslation.objects.get_or_create( - parent=mf, language_code=tr['lang']) - mt.caption = tr['caption'] - mt.description = tr.get('description', None) + mt, mt_created = MediaFileTranslation.objects.get_or_create( + parent=mf, language_code=tr["lang"] + ) + mt.caption = tr["caption"] + mt.description = tr.get("description", None) mt.save() # Add categories mf.categories = ( category_id_map[cat_id] - for cat_id in info.get('categories', [])) + for cat_id in info.get("categories", []) + ) except Exception: pass if not found_metadata: mt = MediaFileTranslation() mt.parent = mf - mt.caption = fname.replace('_', ' ') + mt.caption = fname.replace("_", " ") mt.save() if category: @@ -139,10 +137,14 @@ def import_zipfile(category_id, overwrite, data): def export_zipfile(site, queryset): now = timezone.now() zip_name = "export_%s_%04d%02d%02d.zip" % ( - slugify(site.domain), now.year, now.month, now.day) + slugify(site.domain), + now.year, + now.month, + now.day, + ) zip_data = open(os.path.join(django_settings.MEDIA_ROOT, zip_name), "w") - zip_file = zipfile.ZipFile(zip_data, 'w', allowZip64=True) + zip_file = zipfile.ZipFile(zip_data, "w", allowZip64=True) # Save the used categories in the zip file's global comment used_categories = set() @@ -151,28 +153,36 @@ def export_zipfile(site, queryset): used_categories.update(cat.path_list()) info = { - 'export_magic': export_magic, - 'categories': [{ - 'id': cat.id, - 'title': cat.title, - 'slug': cat.slug, - 'parent': cat.parent_id or 0, - 'level': len(cat.path_list()), - } for cat in used_categories], + "export_magic": export_magic, + "categories": [ + { + "id": cat.id, + "title": cat.title, + "slug": cat.slug, + "parent": cat.parent_id or 0, + "level": len(cat.path_list()), + } + for cat in used_categories + ], } zip_file.comment = json.dumps(info) for mf in queryset: ctime = time.localtime(os.stat(mf.file.path).st_ctime) - info = json.dumps({ - 'copyright': mf.copyright, - 'categories': [cat.id for cat in mf.categories.all()], - 'translations': [{ - 'lang': t.language_code, - 'caption': t.caption, - 'description': t.description, - } for t in mf.translations.all()], - }) + info = json.dumps( + { + "copyright": mf.copyright, + "categories": [cat.id for cat in mf.categories.all()], + "translations": [ + { + "lang": t.language_code, + "caption": t.caption, + "description": t.description, + } + for t in mf.translations.all() + ], + } + ) with open(mf.file.path, "r") as file_data: zip_info = zipfile.ZipInfo( @@ -183,10 +193,13 @@ def export_zipfile(site, queryset): ctime.tm_mday, ctime.tm_hour, ctime.tm_min, - ctime.tm_sec)) + ctime.tm_sec, + ), + ) zip_info.comment = info zip_file.writestr(zip_info, file_data.read()) return zip_name + # ------------------------------------------------------------------------ diff --git a/feincms/module/mixins.py b/feincms/module/mixins.py index ed873334d..56f2d33b5 100644 --- a/feincms/module/mixins.py +++ b/feincms/module/mixins.py @@ -79,7 +79,7 @@ class ContentObjectMixin(TemplateResponseMixin): context_object_name = None def handler(self, request, *args, **kwargs): - if not hasattr(self.request, '_feincms_extra_context'): + if not hasattr(self.request, "_feincms_extra_context"): self.request._feincms_extra_context = {} r = self.run_request_processors() @@ -121,7 +121,7 @@ def get_template_names(self): def get_context_data(self, **kwargs): context = self.request._feincms_extra_context - context[self.context_object_name or 'feincms_object'] = self.object + context[self.context_object_name or "feincms_object"] = self.object context.update(kwargs) return super(ContentObjectMixin, self).get_context_data(**context) @@ -140,7 +140,7 @@ def run_request_processors(self): also return a ``HttpResponse`` for shortcutting the rendering and returning that response immediately to the client. """ - if not getattr(self.object, 'request_processors', None): + if not getattr(self.object, "request_processors", None): return for fn in reversed(list(self.object.request_processors.values())): @@ -154,7 +154,7 @@ def run_response_processors(self, response): processors are called to modify the response, eg. for setting cache or expiration headers, keeping statistics, etc. """ - if not getattr(self.object, 'response_processors', None): + if not getattr(self.object, "response_processors", None): return for fn in self.object.response_processors.values(): @@ -172,8 +172,9 @@ def process_content_types(self): # did any content type successfully end processing? successful = False - for content in self.object.content.all_of_type(tuple( - self.object._feincms_content_types_with_process)): + for content in self.object.content.all_of_type( + tuple(self.object._feincms_content_types_with_process) + ): try: r = content.process(self.request, view=self) @@ -191,15 +192,17 @@ def process_content_types(self): extra_context = self.request._feincms_extra_context - if (not settings.FEINCMS_ALLOW_EXTRA_PATH and - extra_context.get('extra_path', '/') != '/' and - # XXX Already inside application content. I'm not sure - # whether this fix is really correct... - not extra_context.get('app_config')): - raise Http404(str('Not found (extra_path %r on %r)') % ( - extra_context.get('extra_path', '/'), - self.object, - )) + if ( + not settings.FEINCMS_ALLOW_EXTRA_PATH + and extra_context.get("extra_path", "/") != "/" + # XXX Already inside application content. I'm not sure + # whether this fix is really correct... + and not extra_context.get("app_config") + ): + raise Http404( + str("Not found (extra_path %r on %r)") + % (extra_context.get("extra_path", "/"), self.object) + ) def finalize_content_types(self, response): """ @@ -207,8 +210,9 @@ def finalize_content_types(self, response): returns the final response. """ - for content in self.object.content.all_of_type(tuple( - self.object._feincms_content_types_with_finalize)): + for content in self.object.content.all_of_type( + tuple(self.object._feincms_content_types_with_finalize) + ): r = content.finalize(self.request, response) if r: diff --git a/feincms/module/page/admin.py b/feincms/module/page/admin.py index 4abea4668..1fde24864 100644 --- a/feincms/module/page/admin.py +++ b/feincms/module/page/admin.py @@ -17,7 +17,7 @@ if settings.FEINCMS_USE_PAGE_ADMIN: ensure_completely_loaded() try: - Page._meta.get_field('template_key') + Page._meta.get_field("template_key") except FieldDoesNotExist: raise ImproperlyConfigured( "The page module requires a 'Page.register_templates()' call " diff --git a/feincms/module/page/extensions/excerpt.py b/feincms/module/page/extensions/excerpt.py index 25fe9004a..6eb0c583d 100644 --- a/feincms/module/page/extensions/excerpt.py +++ b/feincms/module/page/extensions/excerpt.py @@ -13,16 +13,17 @@ class Extension(extensions.Extension): def handle_model(self): self.model.add_to_class( - 'excerpt', + "excerpt", models.TextField( - _('excerpt'), + _("excerpt"), blank=True, help_text=_( - 'Add a brief excerpt summarizing the content' - ' of this page.'))) + "Add a brief excerpt summarizing the content" " of this page." + ), + ), + ) def handle_modeladmin(self, modeladmin): - modeladmin.add_extension_options(_('Excerpt'), { - 'fields': ('excerpt',), - 'classes': ('collapse',), - }) + modeladmin.add_extension_options( + _("Excerpt"), {"fields": ("excerpt",), "classes": ("collapse",)} + ) diff --git a/feincms/module/page/extensions/navigation.py b/feincms/module/page/extensions/navigation.py index f44e8f22b..d1564fcca 100644 --- a/feincms/module/page/extensions/navigation.py +++ b/feincms/module/page/extensions/navigation.py @@ -30,7 +30,7 @@ class TypeRegistryMetaClass(type): """ def __init__(cls, name, bases, attrs): - if not hasattr(cls, 'types'): + if not hasattr(cls, "types"): cls.types = [] else: cls.types.append(cls) @@ -46,11 +46,12 @@ class PagePretender(object): parameters on creation: title, url, level. If using the translation extension, also add language. """ + pk = None # emulate mptt properties to get the template tags working class _mptt_meta: - level_attr = 'level' + level_attr = "level" def __init__(self, **kwargs): for k, v in kwargs.items(): @@ -86,7 +87,7 @@ class NavigationExtension(six.with_metaclass(TypeRegistryMetaClass)): The name attribute is shown to the website administrator. """ - name = _('navigation extension') + name = _("navigation extension") def children(self, page, **kwargs): """ @@ -103,51 +104,56 @@ def children(self, page, **kwargs): def navigation_extension_choices(): for ext in NavigationExtension.types: - if (issubclass(ext, NavigationExtension) and - ext is not NavigationExtension): - yield ('%s.%s' % (ext.__module__, ext.__name__), ext.name) + if issubclass(ext, NavigationExtension) and ext is not NavigationExtension: + yield ("%s.%s" % (ext.__module__, ext.__name__), ext.name) def get_extension_class(extension): extension = get_object(extension) if isinstance(extension, types.ModuleType): - return getattr(extension, 'Extension') + return getattr(extension, "Extension") return extension class Extension(extensions.Extension): - ident = 'navigation' # TODO actually use this + ident = "navigation" # TODO actually use this navigation_extensions = None @cached_property def _extensions(self): if self.navigation_extensions is None: return OrderedDict( - ('%s.%s' % (ext.__module__, ext.__name__), ext) + ("%s.%s" % (ext.__module__, ext.__name__), ext) for ext in NavigationExtension.types if ( - issubclass(ext, NavigationExtension) and - ext is not NavigationExtension)) + issubclass(ext, NavigationExtension) + and ext is not NavigationExtension + ) + ) else: return OrderedDict( - ('%s.%s' % (ext.__module__, ext.__name__), ext) - for ext - in map(get_extension_class, self.navigation_extensions)) + ("%s.%s" % (ext.__module__, ext.__name__), ext) + for ext in map(get_extension_class, self.navigation_extensions) + ) def handle_model(self): - choices = [ - (path, ext.name) for path, ext in self._extensions.items()] + choices = [(path, ext.name) for path, ext in self._extensions.items()] self.model.add_to_class( - 'navigation_extension', + "navigation_extension", models.CharField( - _('navigation extension'), + _("navigation extension"), choices=choices, - blank=True, null=True, max_length=200, + blank=True, + null=True, + max_length=200, help_text=_( - 'Select the module providing subpages for this page if' - ' you need to customize the navigation.'))) + "Select the module providing subpages for this page if" + " you need to customize the navigation." + ), + ), + ) extension = self @@ -169,7 +175,7 @@ def extended_navigation(self, **kwargs): return self.children.in_navigation() def handle_modeladmin(self, modeladmin): - modeladmin.add_extension_options(_('Navigation extension'), { - 'fields': ('navigation_extension',), - 'classes': ('collapse',), - }) + modeladmin.add_extension_options( + _("Navigation extension"), + {"fields": ("navigation_extension",), "classes": ("collapse",)}, + ) diff --git a/feincms/module/page/extensions/navigationgroups.py b/feincms/module/page/extensions/navigationgroups.py index fc47ec033..eef662295 100644 --- a/feincms/module/page/extensions/navigationgroups.py +++ b/feincms/module/page/extensions/navigationgroups.py @@ -12,24 +12,23 @@ class Extension(extensions.Extension): - ident = 'navigationgroups' - groups = [ - ('default', _('Default')), - ('footer', _('Footer')), - ] + ident = "navigationgroups" + groups = [("default", _("Default")), ("footer", _("Footer"))] def handle_model(self): self.model.add_to_class( - 'navigation_group', + "navigation_group", models.CharField( - _('navigation group'), + _("navigation group"), choices=self.groups, default=self.groups[0][0], max_length=20, blank=True, - db_index=True)) + db_index=True, + ), + ) def handle_modeladmin(self, modeladmin): - modeladmin.add_extension_options('navigation_group') - modeladmin.extend_list('list_display', ['navigation_group']) - modeladmin.extend_list('list_filter', ['navigation_group']) + modeladmin.add_extension_options("navigation_group") + modeladmin.extend_list("list_display", ["navigation_group"]) + modeladmin.extend_list("list_filter", ["navigation_group"]) diff --git a/feincms/module/page/extensions/relatedpages.py b/feincms/module/page/extensions/relatedpages.py index 2ddfb0ef7..2a5cf02d7 100644 --- a/feincms/module/page/extensions/relatedpages.py +++ b/feincms/module/page/extensions/relatedpages.py @@ -12,17 +12,19 @@ class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('related_pages', models.ManyToManyField( - settings.FEINCMS_DEFAULT_PAGE_MODEL, - blank=True, - related_name='%(app_label)s_%(class)s_related', - help_text=_( - 'Select pages that should be listed as related content.'))) + self.model.add_to_class( + "related_pages", + models.ManyToManyField( + settings.FEINCMS_DEFAULT_PAGE_MODEL, + blank=True, + related_name="%(app_label)s_%(class)s_related", + help_text=_("Select pages that should be listed as related content."), + ), + ) def handle_modeladmin(self, modeladmin): - modeladmin.extend_list('filter_horizontal', ['related_pages']) + modeladmin.extend_list("filter_horizontal", ["related_pages"]) - modeladmin.add_extension_options(_('Related pages'), { - 'fields': ('related_pages',), - 'classes': ('collapse',), - }) + modeladmin.add_extension_options( + _("Related pages"), {"fields": ("related_pages",), "classes": ("collapse",)} + ) diff --git a/feincms/module/page/extensions/sites.py b/feincms/module/page/extensions/sites.py index b0b25d245..69ad32cd9 100644 --- a/feincms/module/page/extensions/sites.py +++ b/feincms/module/page/extensions/sites.py @@ -16,14 +16,18 @@ def current_site(queryset): class Extension(extensions.Extension): def handle_model(self): self.model.add_to_class( - 'site', + "site", models.ForeignKey( - Site, verbose_name=_('Site'), default=settings.SITE_ID, - on_delete=models.CASCADE)) + Site, + verbose_name=_("Site"), + default=settings.SITE_ID, + on_delete=models.CASCADE, + ), + ) - PageManager.add_to_active_filters(current_site, key='current_site') + PageManager.add_to_active_filters(current_site, key="current_site") def handle_modeladmin(self, modeladmin): - modeladmin.extend_list('list_display', ['site']) - modeladmin.extend_list('list_filter', ['site']) - modeladmin.add_extension_options('site') + modeladmin.extend_list("list_display", ["site"]) + modeladmin.extend_list("list_filter", ["site"]) + modeladmin.add_extension_options("site") diff --git a/feincms/module/page/extensions/symlinks.py b/feincms/module/page/extensions/symlinks.py index c5e4c095b..549d03342 100644 --- a/feincms/module/page/extensions/symlinks.py +++ b/feincms/module/page/extensions/symlinks.py @@ -14,26 +14,29 @@ class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('symlinked_page', models.ForeignKey( - 'self', - blank=True, - null=True, - on_delete=models.CASCADE, - related_name='%(app_label)s_%(class)s_symlinks', - verbose_name=_('symlinked page'), - help_text=_('All content is inherited from this page if given.'))) + self.model.add_to_class( + "symlinked_page", + models.ForeignKey( + "self", + blank=True, + null=True, + on_delete=models.CASCADE, + related_name="%(app_label)s_%(class)s_symlinks", + verbose_name=_("symlinked page"), + help_text=_("All content is inherited from this page if given."), + ), + ) @monkeypatch_property(self.model) def content(self): - if not hasattr(self, '_content_proxy'): + if not hasattr(self, "_content_proxy"): if self.symlinked_page: - self._content_proxy = self.content_proxy_class( - self.symlinked_page) + self._content_proxy = self.content_proxy_class(self.symlinked_page) else: self._content_proxy = self.content_proxy_class(self) return self._content_proxy def handle_modeladmin(self, modeladmin): - modeladmin.extend_list('raw_id_fields', ['symlinked_page']) - modeladmin.add_extension_options('symlinked_page') + modeladmin.extend_list("raw_id_fields", ["symlinked_page"]) + modeladmin.add_extension_options("symlinked_page") diff --git a/feincms/module/page/extensions/titles.py b/feincms/module/page/extensions/titles.py index 68529b113..d70da3c43 100644 --- a/feincms/module/page/extensions/titles.py +++ b/feincms/module/page/extensions/titles.py @@ -15,20 +15,30 @@ class Extension(extensions.Extension): def handle_model(self): - self.model.add_to_class('_content_title', models.TextField( - _('content title'), - blank=True, - help_text=_( - 'The first line is the main title, the following' - ' lines are subtitles.'))) - - self.model.add_to_class('_page_title', models.CharField( - _('page title'), - max_length=69, - blank=True, - help_text=_( - 'Page title for browser window. Same as title by' - ' default. Must be 69 characters or fewer.'))) + self.model.add_to_class( + "_content_title", + models.TextField( + _("content title"), + blank=True, + help_text=_( + "The first line is the main title, the following" + " lines are subtitles." + ), + ), + ) + + self.model.add_to_class( + "_page_title", + models.CharField( + _("page title"), + max_length=69, + blank=True, + help_text=_( + "Page title for browser window. Same as title by" + " default. Must be 69 characters or fewer." + ), + ), + ) @monkeypatch_property(self.model) def page_title(self): @@ -54,10 +64,10 @@ def content_title(self): @monkeypatch_property(self.model) def content_subtitle(self): - return '\n'.join(self._content_title.splitlines()[1:]) + return "\n".join(self._content_title.splitlines()[1:]) def handle_modeladmin(self, modeladmin): - modeladmin.add_extension_options(_('Titles'), { - 'fields': ('_content_title', '_page_title'), - 'classes': ('collapse',), - }) + modeladmin.add_extension_options( + _("Titles"), + {"fields": ("_content_title", "_page_title"), "classes": ("collapse",)}, + ) diff --git a/feincms/module/page/forms.py b/feincms/module/page/forms.py index 1e7f3abd8..9d115b503 100644 --- a/feincms/module/page/forms.py +++ b/feincms/module/page/forms.py @@ -21,28 +21,38 @@ class RedirectToWidget(ForeignKeyRawIdWidget): def label_for_value(self, value): match = re.match( # XXX this regex would be available as .models.REDIRECT_TO_RE - r'^(?P\w+).(?P\w+):(?P\d+)$', - value) + r"^(?P\w+).(?P\w+):(?P\d+)$", + value, + ) if match: matches = match.groupdict() - model = apps.get_model(matches['app_label'], matches['model_name']) + model = apps.get_model(matches["app_label"], matches["model_name"]) try: - instance = model._default_manager.get(pk=int(matches['pk'])) - return ' %s (%s)' % ( - instance, instance.get_absolute_url()) + instance = model._default_manager.get(pk=int(matches["pk"])) + return " %s (%s)" % ( + instance, + instance.get_absolute_url(), + ) except model.DoesNotExist: pass - return '' + return "" # ------------------------------------------------------------------------ class PageAdminForm(MPTTAdminForm): never_copy_fields = ( - 'title', 'slug', 'parent', 'active', 'override_url', - 'translation_of', '_content_title', '_page_title') + "title", + "slug", + "parent", + "active", + "override_url", + "translation_of", + "_content_title", + "_page_title", + ) @property def page_model(self): @@ -55,12 +65,11 @@ def page_manager(self): def __init__(self, *args, **kwargs): ensure_completely_loaded() - if 'initial' in kwargs: - if 'parent' in kwargs['initial']: + if "initial" in kwargs: + if "parent" in kwargs["initial"]: # Prefill a few form values from the parent page try: - page = self.page_manager.get( - pk=kwargs['initial']['parent']) + page = self.page_manager.get(pk=kwargs["initial"]["parent"]) data = model_to_dict(page) @@ -73,76 +82,80 @@ def __init__(self, *args, **kwargs): if field in data: del data[field] - data.update(kwargs['initial']) + data.update(kwargs["initial"]) if page.template.child_template: - data['template_key'] = page.template.child_template - kwargs['initial'] = data + data["template_key"] = page.template.child_template + kwargs["initial"] = data except self.page_model.DoesNotExist: pass - elif 'translation_of' in kwargs['initial']: + elif "translation_of" in kwargs["initial"]: # Only if translation extension is active try: - page = self.page_manager.get( - pk=kwargs['initial']['translation_of']) + page = self.page_manager.get(pk=kwargs["initial"]["translation_of"]) original = page.original_translation data = { - 'translation_of': original.id, - 'template_key': original.template_key, - 'active': original.active, - 'in_navigation': original.in_navigation, + "translation_of": original.id, + "template_key": original.template_key, + "active": original.active, + "in_navigation": original.in_navigation, } if original.parent: try: - data['parent'] = original.parent.get_translation( - kwargs['initial']['language'] + data["parent"] = original.parent.get_translation( + kwargs["initial"]["language"] ).id except self.page_model.DoesNotExist: # ignore this -- the translation does not exist pass - data.update(kwargs['initial']) - kwargs['initial'] = data + data.update(kwargs["initial"]) + kwargs["initial"] = data except (AttributeError, self.page_model.DoesNotExist): pass # Not required, only a nice-to-have for the `redirect_to` field - modeladmin = kwargs.pop('modeladmin', None) + modeladmin = kwargs.pop("modeladmin", None) super(PageAdminForm, self).__init__(*args, **kwargs) - if modeladmin and 'redirect_to' in self.fields: + if modeladmin and "redirect_to" in self.fields: # Note: Using `parent` is not strictly correct, but we can be # sure that `parent` always points to another page instance, # and that's good enough for us. - field = self.page_model._meta.get_field('parent') - self.fields['redirect_to'].widget = RedirectToWidget( - field.remote_field if hasattr(field, 'remote_field') else field.rel, # noqa - modeladmin.admin_site) - - if 'template_key' in self.fields: + field = self.page_model._meta.get_field("parent") + self.fields["redirect_to"].widget = RedirectToWidget( + field.remote_field + if hasattr(field, "remote_field") + else field.rel, # noqa + modeladmin.admin_site, + ) + + if "template_key" in self.fields: choices = [] for key, template_name in self.page_model.TEMPLATE_CHOICES: template = self.page_model._feincms_templates[key] pages_for_template = self.page_model._default_manager.filter( - template_key=key) - pk = kwargs['instance'].pk if kwargs.get('instance') else None + template_key=key + ) + pk = kwargs["instance"].pk if kwargs.get("instance") else None other_pages_for_template = pages_for_template.exclude(pk=pk) if template.singleton and other_pages_for_template.exists(): continue # don't allow selection of singleton if in use if template.preview_image: - choices.append(( - template.key, - mark_safe('%s %s' % ( - template.preview_image, + choices.append( + ( template.key, - template.title, - )) - )) + mark_safe( + '%s %s' + % (template.preview_image, template.key, template.title) + ), + ) + ) else: choices.append((template.key, template.title)) - self.fields['template_key'].choices = choices + self.fields["template_key"].choices = choices def clean(self): cleaned_data = super(PageAdminForm, self).clean() @@ -160,18 +173,21 @@ def clean(self): current_id = self.instance.id active_pages = active_pages.exclude(id=current_id) - sites_is_installed = apps.is_installed('django.contrib.sites') - if sites_is_installed and 'site' in cleaned_data: - active_pages = active_pages.filter(site=cleaned_data['site']) + sites_is_installed = apps.is_installed("django.contrib.sites") + if sites_is_installed and "site" in cleaned_data: + active_pages = active_pages.filter(site=cleaned_data["site"]) # Convert PK in redirect_to field to something nicer for the future - redirect_to = cleaned_data.get('redirect_to') - if redirect_to and re.match(r'^\d+$', redirect_to): + redirect_to = cleaned_data.get("redirect_to") + if redirect_to and re.match(r"^\d+$", redirect_to): opts = self.page_model._meta - cleaned_data['redirect_to'] = '%s.%s:%s' % ( - opts.app_label, opts.model_name, redirect_to) + cleaned_data["redirect_to"] = "%s.%s:%s" % ( + opts.app_label, + opts.model_name, + redirect_to, + ) - if 'active' in cleaned_data and not cleaned_data['active']: + if "active" in cleaned_data and not cleaned_data["active"]: # If the current item is inactive, we do not need to conduct # further validation. Note that we only check for the flag, not # for any other active filters. This is because we do not want @@ -179,12 +195,12 @@ def clean(self): # really won't be active at the same time. return cleaned_data - if 'override_url' in cleaned_data and cleaned_data['override_url']: - if active_pages.filter( - _cached_url=cleaned_data['override_url']).count(): - self._errors['override_url'] = self.error_class([ - _('This URL is already taken by an active page.')]) - del cleaned_data['override_url'] + if "override_url" in cleaned_data and cleaned_data["override_url"]: + if active_pages.filter(_cached_url=cleaned_data["override_url"]).count(): + self._errors["override_url"] = self.error_class( + [_("This URL is already taken by an active page.")] + ) + del cleaned_data["override_url"] return cleaned_data @@ -193,24 +209,27 @@ def clean(self): parent = self.page_manager.get(pk=current_id).parent else: # The user tries to create a new page - parent = cleaned_data['parent'] + parent = cleaned_data["parent"] if parent: - new_url = '%s%s/' % (parent._cached_url, cleaned_data['slug']) + new_url = "%s%s/" % (parent._cached_url, cleaned_data["slug"]) else: - new_url = '/%s/' % cleaned_data['slug'] + new_url = "/%s/" % cleaned_data["slug"] if active_pages.filter(_cached_url=new_url).count(): - self._errors['active'] = self.error_class([ - _('This URL is already taken by another active page.')]) - del cleaned_data['active'] + self._errors["active"] = self.error_class( + [_("This URL is already taken by another active page.")] + ) + del cleaned_data["active"] if parent and parent.template.enforce_leaf: - self._errors['parent'] = self.error_class( - [_('This page does not allow attachment of child pages')]) - del cleaned_data['parent'] + self._errors["parent"] = self.error_class( + [_("This page does not allow attachment of child pages")] + ) + del cleaned_data["parent"] return cleaned_data + # ------------------------------------------------------------------------ # ------------------------------------------------------------------------ diff --git a/feincms/module/page/modeladmins.py b/feincms/module/page/modeladmins.py index a913c0db8..4a6847537 100644 --- a/feincms/module/page/modeladmins.py +++ b/feincms/module/page/modeladmins.py @@ -15,6 +15,7 @@ from django.http import HttpResponseRedirect from django.utils.functional import curry from django.utils.translation import ugettext_lazy as _ + try: from django.urls import reverse except ImportError: @@ -41,57 +42,58 @@ class Media: fieldset_insertion_index = 2 fieldsets = [ - (None, { - 'fields': [ - ('title', 'slug'), - ('active', 'in_navigation'), - ], - }), - (_('Other options'), { - 'classes': ['collapse'], - 'fields': [ - 'template_key', 'parent', 'override_url', 'redirect_to'], - }), + (None, {"fields": [("title", "slug"), ("active", "in_navigation")]}), + ( + _("Other options"), + { + "classes": ["collapse"], + "fields": ["template_key", "parent", "override_url", "redirect_to"], + }, + ), # <-- insertion point, extensions appear here, see insertion_index # above item_editor.FEINCMS_CONTENT_FIELDSET, ] readonly_fields = [] list_display = [ - 'short_title', 'is_visible_admin', 'in_navigation_toggle', 'template'] - list_filter = ['active', 'in_navigation', 'template_key', 'parent'] - search_fields = ['title', 'slug'] - prepopulated_fields = {'slug': ('title',)} + "short_title", + "is_visible_admin", + "in_navigation_toggle", + "template", + ] + list_filter = ["active", "in_navigation", "template_key", "parent"] + search_fields = ["title", "slug"] + prepopulated_fields = {"slug": ("title",)} - raw_id_fields = ['parent'] - radio_fields = {'template_key': admin.HORIZONTAL} + raw_id_fields = ["parent"] + radio_fields = {"template_key": admin.HORIZONTAL} @classmethod def add_extension_options(cls, *f): - if isinstance(f[-1], dict): # called with a fieldset + if isinstance(f[-1], dict): # called with a fieldset cls.fieldsets.insert(cls.fieldset_insertion_index, f) - f[1]['classes'] = list(f[1].get('classes', [])) - f[1]['classes'].append('collapse') - else: # assume called with "other" fields - cls.fieldsets[1][1]['fields'].extend(f) + f[1]["classes"] = list(f[1].get("classes", [])) + f[1]["classes"].append("collapse") + else: # assume called with "other" fields + cls.fieldsets[1][1]["fields"].extend(f) def __init__(self, model, admin_site): ensure_completely_loaded() - if len(model._feincms_templates) > 4 and \ - 'template_key' in self.radio_fields: - del(self.radio_fields['template_key']) + if len(model._feincms_templates) > 4 and "template_key" in self.radio_fields: + del (self.radio_fields["template_key"]) super(PageAdmin, self).__init__(model, admin_site) in_navigation_toggle = tree_editor.ajax_editable_boolean( - 'in_navigation', _('in navigation')) + "in_navigation", _("in navigation") + ) def get_readonly_fields(self, request, obj=None): readonly = super(PageAdmin, self).get_readonly_fields(request, obj=obj) if not settings.FEINCMS_SINGLETON_TEMPLATE_CHANGE_ALLOWED: if obj and obj.template and obj.template.singleton: - return tuple(readonly) + ('template_key',) + return tuple(readonly) + ("template_key",) return readonly def get_form(self, *args, **kwargs): @@ -99,11 +101,12 @@ def get_form(self, *args, **kwargs): return curry(form, modeladmin=self) def _actions_column(self, page): - addable = getattr(page, 'feincms_addable', True) + addable = getattr(page, "feincms_addable", True) preview_url = "../../r/%s/%s/" % ( ContentType.objects.get_for_model(self.model).id, - page.id) + page.id, + ) actions = super(PageAdmin, self)._actions_column(page) if addable: @@ -112,61 +115,63 @@ def _actions_column(self, page): 0, '' '%s' - '' % ( + "" + % ( page.pk, - _('Add child page'), - static('feincms/img/icon_addlink.gif'), - _('Add child page'), - ) + _("Add child page"), + static("feincms/img/icon_addlink.gif"), + _("Add child page"), + ), ) actions.insert( 0, '' '%s' - '' % ( + "" + % ( preview_url, - _('View on site'), - static('feincms/img/selector-search.gif'), - _('View on site'), - ) + _("View on site"), + static("feincms/img/selector-search.gif"), + _("View on site"), + ), ) return actions def add_view(self, request, **kwargs): - kwargs['form_url'] = request.get_full_path() # Preserve GET parameters - if 'translation_of' in request.GET and 'language' in request.GET: + kwargs["form_url"] = request.get_full_path() # Preserve GET parameters + if "translation_of" in request.GET and "language" in request.GET: try: original = self.model._tree_manager.get( - pk=request.GET.get('translation_of')) + pk=request.GET.get("translation_of") + ) except (AttributeError, self.model.DoesNotExist): pass else: - language_code = request.GET['language'] - language = dict( - django_settings.LANGUAGES).get(language_code, '') - kwargs['extra_context'] = { - 'adding_translation': True, - 'title': _( - 'Add %(language)s translation of "%(page)s"') % { - 'language': language, - 'page': original, - }, - 'language_name': language, - 'translation_of': original, + language_code = request.GET["language"] + language = dict(django_settings.LANGUAGES).get(language_code, "") + kwargs["extra_context"] = { + "adding_translation": True, + "title": _('Add %(language)s translation of "%(page)s"') + % {"language": language, "page": original}, + "language_name": language, + "translation_of": original, } return super(PageAdmin, self).add_view(request, **kwargs) def response_add(self, request, obj, *args, **kwargs): - response = super(PageAdmin, self).response_add( - request, obj, *args, **kwargs) - if ('parent' in request.GET and - '_addanother' in request.POST and - response.status_code in (301, 302)): + response = super(PageAdmin, self).response_add(request, obj, *args, **kwargs) + if ( + "parent" in request.GET + and "_addanother" in request.POST + and response.status_code in (301, 302) + ): # Preserve GET parameters if we are about to add another page - response['Location'] += '?parent=%s' % request.GET['parent'] + response["Location"] += "?parent=%s" % request.GET["parent"] - if ('translation_of' in request.GET and - '_copy_content_from_original' in request.POST): + if ( + "translation_of" in request.GET + and "_copy_content_from_original" in request.POST + ): # Copy all contents for content_type in obj._feincms_content_types: if content_type.objects.filter(parent=obj).exists(): @@ -176,14 +181,19 @@ def response_add(self, request, obj, *args, **kwargs): try: original = self.model._tree_manager.get( - pk=request.GET.get('translation_of')) + pk=request.GET.get("translation_of") + ) original = original.original_translation obj.copy_content_from(original) obj.save() - self.message_user(request, _( - 'The content from the original translation has been copied' - ' to the newly created page.')) + self.message_user( + request, + _( + "The content from the original translation has been copied" + " to the newly created page." + ), + ) except (AttributeError, self.model.DoesNotExist): pass @@ -191,18 +201,14 @@ def response_add(self, request, obj, *args, **kwargs): def change_view(self, request, object_id, **kwargs): try: - return super(PageAdmin, self).change_view( - request, object_id, **kwargs) + return super(PageAdmin, self).change_view(request, object_id, **kwargs) except PermissionDenied: messages.add_message( request, messages.ERROR, - _( - "You don't have the necessary permissions to edit this" - " object" - ) + _("You don't have the necessary permissions to edit this" " object"), ) - return HttpResponseRedirect(reverse('admin:page_page_changelist')) + return HttpResponseRedirect(reverse("admin:page_page_changelist")) def has_delete_permission(self, request, obj=None): if not settings.FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED: @@ -212,7 +218,8 @@ def has_delete_permission(self, request, obj=None): def changelist_view(self, request, *args, **kwargs): _local.visible_pages = list( - self.model.objects.active().values_list('id', flat=True)) + self.model.objects.active().values_list("id", flat=True) + ) return super(PageAdmin, self).changelist_view(request, *args, **kwargs) def is_visible_admin(self, page): @@ -225,30 +232,36 @@ def is_visible_admin(self, page): if page.id in _local.visible_pages: _local.visible_pages.remove(page.id) return tree_editor.ajax_editable_boolean_cell( - page, 'active', override=False, text=_('inherited')) + page, "active", override=False, text=_("inherited") + ) if page.active and page.id not in _local.visible_pages: # is active but should not be shown, so visibility limited by # extension: show a "not active" return tree_editor.ajax_editable_boolean_cell( - page, 'active', override=False, text=_('extensions')) + page, "active", override=False, text=_("extensions") + ) + + return tree_editor.ajax_editable_boolean_cell(page, "active") - return tree_editor.ajax_editable_boolean_cell(page, 'active') - is_visible_admin.short_description = _('is active') - is_visible_admin.editable_boolean_field = 'active' + is_visible_admin.short_description = _("is active") + is_visible_admin.editable_boolean_field = "active" # active toggle needs more sophisticated result function def is_visible_recursive(self, page): # Have to refresh visible_pages here, because TreeEditor.toggle_boolean # will have changed the value when inside this code path. _local.visible_pages = list( - self.model.objects.active().values_list('id', flat=True)) + self.model.objects.active().values_list("id", flat=True) + ) retval = [] for c in page.get_descendants(include_self=True): retval.append(self.is_visible_admin(c)) return retval + is_visible_admin.editable_boolean_result = is_visible_recursive + # ------------------------------------------------------------------------ # ------------------------------------------------------------------------ diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index 1f6f5262f..10932feec 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -10,6 +10,7 @@ from django.http import Http404 from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ + try: from django.urls import reverse except ImportError: @@ -22,9 +23,7 @@ from feincms.module.mixins import ContentModelMixin from feincms.module.page import processors from feincms.utils.managers import ActiveAwareContentManagerMixin -from feincms.utils import ( - shorten_string, match_model_string, get_model_instance -) +from feincms.utils import shorten_string, match_model_string, get_model_instance # ------------------------------------------------------------------------ @@ -35,8 +34,7 @@ class BasePageManager(ActiveAwareContentManagerMixin, TreeManager): """ # The fields which should be excluded when creating a copy. - exclude_from_copy = [ - 'id', 'tree_id', 'lft', 'rght', 'level', 'redirect_to'] + exclude_from_copy = ["id", "tree_id", "lft", "rght", "level", "redirect_to"] def page_for_path(self, path, raise404=False): """ @@ -47,14 +45,13 @@ def page_for_path(self, path, raise404=False): Page.objects.page_for_path(request.path) """ - stripped = path.strip('/') + stripped = path.strip("/") try: - page = self.active().get( - _cached_url='/%s/' % stripped if stripped else '/') + page = self.active().get(_cached_url="/%s/" % stripped if stripped else "/") if not page.are_ancestors_active(): - raise self.model.DoesNotExist('Parents are inactive.') + raise self.model.DoesNotExist("Parents are inactive.") return page @@ -75,22 +72,23 @@ def best_match_for_path(self, path, raise404=False): page with url '/photos/album/'. """ - paths = ['/'] - path = path.strip('/') + paths = ["/"] + path = path.strip("/") if path: - tokens = path.split('/') - paths += [ - '/%s/' % '/'.join(tokens[:i]) - for i in range(1, len(tokens) + 1)] + tokens = path.split("/") + paths += ["/%s/" % "/".join(tokens[:i]) for i in range(1, len(tokens) + 1)] try: - page = self.active().filter(_cached_url__in=paths).extra( - select={'_url_length': 'LENGTH(_cached_url)'} - ).order_by('-_url_length')[0] + page = ( + self.active() + .filter(_cached_url__in=paths) + .extra(select={"_url_length": "LENGTH(_cached_url)"}) + .order_by("-_url_length")[0] + ) if not page.are_ancestors_active(): - raise IndexError('Parents are inactive.') + raise IndexError("Parents are inactive.") return page @@ -114,8 +112,7 @@ def toplevel_navigation(self): return self.in_navigation().filter(parent__isnull=True) - def for_request(self, request, raise404=False, best_match=False, - path=None): + def for_request(self, request, raise404=False, best_match=False, path=None): """ Return a page for the request @@ -129,15 +126,15 @@ def for_request(self, request, raise404=False, best_match=False, could be determined. """ - if not hasattr(request, '_feincms_page'): + if not hasattr(request, "_feincms_page"): path = path or request.path_info or request.path if best_match: request._feincms_page = self.best_match_for_path( - path, raise404=raise404) + path, raise404=raise404 + ) else: - request._feincms_page = self.page_for_path( - path, raise404=raise404) + request._feincms_page = self.page_for_path(path, raise404=raise404) return request._feincms_page @@ -147,45 +144,65 @@ class PageManager(BasePageManager): pass -PageManager.add_to_active_filters(Q(active=True), key='is_active') +PageManager.add_to_active_filters(Q(active=True), key="is_active") # ------------------------------------------------------------------------ @python_2_unicode_compatible class BasePage(create_base_model(MPTTModel), ContentModelMixin): - active = models.BooleanField(_('active'), default=True) + active = models.BooleanField(_("active"), default=True) # structure and navigation - title = models.CharField(_('title'), max_length=200, help_text=_( - 'This title is also used for navigation menu items.')) + title = models.CharField( + _("title"), + max_length=200, + help_text=_("This title is also used for navigation menu items."), + ) slug = models.SlugField( - _('slug'), max_length=150, - help_text=_('This is used to build the URL for this page')) + _("slug"), + max_length=150, + help_text=_("This is used to build the URL for this page"), + ) parent = models.ForeignKey( - 'self', verbose_name=_('Parent'), blank=True, + "self", + verbose_name=_("Parent"), + blank=True, on_delete=models.CASCADE, - null=True, related_name='children') + null=True, + related_name="children", + ) # Custom list_filter - see admin/filterspecs.py parent.parent_filter = True - in_navigation = models.BooleanField(_('in navigation'), default=False) + in_navigation = models.BooleanField(_("in navigation"), default=False) override_url = models.CharField( - _('override URL'), max_length=255, - blank=True, help_text=_( - 'Override the target URL. Be sure to include slashes at the ' - 'beginning and at the end if it is a local URL. This ' - 'affects both the navigation and subpages\' URLs.')) + _("override URL"), + max_length=255, + blank=True, + help_text=_( + "Override the target URL. Be sure to include slashes at the " + "beginning and at the end if it is a local URL. This " + "affects both the navigation and subpages' URLs." + ), + ) redirect_to = models.CharField( - _('redirect to'), max_length=255, + _("redirect to"), + max_length=255, blank=True, help_text=_( - 'Target URL for automatic redirects' - ' or the primary key of a page.')) + "Target URL for automatic redirects" " or the primary key of a page." + ), + ) _cached_url = models.CharField( - _('Cached URL'), max_length=255, blank=True, - editable=False, default='', db_index=True) + _("Cached URL"), + max_length=255, + blank=True, + editable=False, + default="", + db_index=True, + ) class Meta: - ordering = ['tree_id', 'lft'] + ordering = ["tree_id", "lft"] abstract = True objects = PageManager() @@ -206,11 +223,11 @@ def is_active(self): return False pages = self.__class__.objects.active().filter( - tree_id=self.tree_id, - lft__lte=self.lft, - rght__gte=self.rght) + tree_id=self.tree_id, lft__lte=self.lft, rght__gte=self.rght + ) return pages.count() > self.level - is_active.short_description = _('is active') + + is_active.short_description = _("is active") def are_ancestors_active(self): """ @@ -228,8 +245,9 @@ def short_title(self): Title shortened for display. """ return shorten_string(self.title) - short_title.admin_order_field = 'title' - short_title.short_description = _('title') + + short_title.admin_order_field = "title" + short_title.short_description = _("title") def __init__(self, *args, **kwargs): super(BasePage, self).__init__(*args, **kwargs) @@ -250,9 +268,9 @@ def save(self, *args, **kwargs): if self.override_url: self._cached_url = self.override_url elif self.is_root_node(): - self._cached_url = '/%s/' % self.slug + self._cached_url = "/%s/" % self.slug else: - self._cached_url = '%s%s/' % (self.parent._cached_url, self.slug) + self._cached_url = "%s%s/" % (self.parent._cached_url, self.slug) cached_page_urls[self.id] = self._cached_url super(BasePage, self).save(*args, **kwargs) @@ -264,29 +282,35 @@ def save(self, *args, **kwargs): if self._cached_url == self._original_cached_url: return - pages = self.get_descendants().order_by('lft') + pages = self.get_descendants().order_by("lft") for page in pages: if page.override_url: page._cached_url = page.override_url else: # cannot be root node by definition - page._cached_url = '%s%s/' % ( + page._cached_url = "%s%s/" % ( cached_page_urls[page.parent_id], - page.slug) + page.slug, + ) cached_page_urls[page.id] = page._cached_url super(BasePage, page).save() # do not recurse + save.alters_data = True def delete(self, *args, **kwargs): if not settings.FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED: if self.template.singleton: - raise PermissionDenied(_( - 'This %(page_class)s uses a singleton template, and ' - 'FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False' % { - 'page_class': self._meta.verbose_name})) + raise PermissionDenied( + _( + "This %(page_class)s uses a singleton template, and " + "FEINCMS_SINGLETON_TEMPLATE_DELETION_ALLOWED=False" + % {"page_class": self._meta.verbose_name} + ) + ) super(BasePage, self).delete(*args, **kwargs) + delete.alters_data = True def get_absolute_url(self): @@ -294,10 +318,10 @@ def get_absolute_url(self): Return the absolute URL of this page. """ # result url never begins or ends with a slash - url = self._cached_url.strip('/') + url = self._cached_url.strip("/") if url: - return reverse('feincms_handler', args=(url,)) - return reverse('feincms_home') + return reverse("feincms_handler", args=(url,)) + return reverse("feincms_home") def get_navigation_url(self): """ @@ -360,18 +384,20 @@ def register_default_processors(cls): Page experience. """ cls.register_request_processor( - processors.redirect_request_processor, key='redirect') + processors.redirect_request_processor, key="redirect" + ) cls.register_request_processor( - processors.extra_context_request_processor, key='extra_context') + processors.extra_context_request_processor, key="extra_context" + ) # ------------------------------------------------------------------------ class Page(BasePage): class Meta: - ordering = ['tree_id', 'lft'] - verbose_name = _('page') - verbose_name_plural = _('pages') - app_label = 'page' + ordering = ["tree_id", "lft"] + verbose_name = _("page") + verbose_name_plural = _("pages") + app_label = "page" # not yet # permissions = (("edit_page", _("Can edit page metadata")),) diff --git a/feincms/module/page/processors.py b/feincms/module/page/processors.py index 44441e824..894e4c0f9 100644 --- a/feincms/module/page/processors.py +++ b/feincms/module/page/processors.py @@ -18,12 +18,14 @@ def redirect_request_processor(page, request): """ target = page.get_redirect_to_target(request) if target: - extra_path = request._feincms_extra_context.get('extra_path', '/') - if extra_path == '/': + extra_path = request._feincms_extra_context.get("extra_path", "/") + if extra_path == "/": return HttpResponseRedirect(target) logger.debug( "Page redirect on '%s' not taken because extra path '%s' present", - page.get_absolute_url(), extra_path) + page.get_absolute_url(), + extra_path, + ) raise Http404() @@ -31,22 +33,24 @@ def extra_context_request_processor(page, request): """ Fills ``request._feincms_extra_context`` with a few useful variables. """ - request._feincms_extra_context.update({ - # XXX This variable name isn't accurate anymore. - 'in_appcontent_subpage': False, - 'extra_path': '/', - }) + request._feincms_extra_context.update( + { + # XXX This variable name isn't accurate anymore. + "in_appcontent_subpage": False, + "extra_path": "/", + } + ) url = page.get_absolute_url() if request.path != url: - request._feincms_extra_context.update({ - 'in_appcontent_subpage': True, - 'extra_path': re.sub( - '^' + re.escape(url.rstrip('/')), - '', - request.path, - ), - }) + request._feincms_extra_context.update( + { + "in_appcontent_subpage": True, + "extra_path": re.sub( + "^" + re.escape(url.rstrip("/")), "", request.path + ), + } + ) def etag_request_processor(page, request): @@ -62,6 +66,7 @@ class DummyResponse(dict): This is a dummy class with enough behaviour of HttpResponse so we can use the condition decorator without too much pain. """ + def has_header(page, what): return False @@ -85,7 +90,8 @@ def lastmodifier(request, page, *args, **kwargs): # the handler if processing is to continue and a non-DummyResponse # (should be a "304 not modified") if the etag matches. rsp = condition(etag_func=etagger, last_modified_func=lastmodifier)( - dummy_response_handler)(request, page) + dummy_response_handler + )(request, page) # If dummy then don't do anything, if a real response, return and # thus shortcut the request processing. @@ -101,7 +107,7 @@ def etag_response_processor(page, request, response): """ etag = page.etag(request) if etag is not None: - response['ETag'] = '"' + etag + '"' + response["ETag"] = '"' + etag + '"' def debug_sql_queries_response_processor(verbose=False, file=sys.stderr): @@ -127,9 +133,10 @@ def processor(page, request, response): import sqlparse def print_sql(x): - return sqlparse.format( - x, reindent=True, keyword_case='upper') + return sqlparse.format(x, reindent=True, keyword_case="upper") + except Exception: + def print_sql(x): return x @@ -140,9 +147,10 @@ def print_sql(x): for q in connection.queries: i += 1 if verbose: - print("%d : [%s]\n%s\n" % ( - i, q['time'], print_sql(q['sql'])), file=file) - time += float(q['time']) + print( + "%d : [%s]\n%s\n" % (i, q["time"], print_sql(q["sql"])), file=file + ) + time += float(q["time"]) print("-" * 60, file=file) print("Total: %d queries, %.3f ms" % (i, time), file=file) diff --git a/feincms/module/page/sitemap.py b/feincms/module/page/sitemap.py index 40f2853d4..c5cbd5850 100644 --- a/feincms/module/page/sitemap.py +++ b/feincms/module/page/sitemap.py @@ -17,10 +17,19 @@ class PageSitemap(Sitemap): The PageSitemap can be used to automatically generate sitemap.xml files for submission to index engines. See http://www.sitemaps.org/ for details. """ - def __init__(self, navigation_only=False, max_depth=0, changefreq=None, - queryset=None, filter=None, extended_navigation=False, - page_model=settings.FEINCMS_DEFAULT_PAGE_MODEL, - *args, **kwargs): + + def __init__( + self, + navigation_only=False, + max_depth=0, + changefreq=None, + queryset=None, + filter=None, + extended_navigation=False, + page_model=settings.FEINCMS_DEFAULT_PAGE_MODEL, + *args, + **kwargs + ): """ The PageSitemap accepts the following parameters for customisation of the resulting sitemap.xml output: @@ -48,7 +57,7 @@ def __init__(self, navigation_only=False, max_depth=0, changefreq=None, if queryset is not None: self.queryset = queryset else: - Page = apps.get_model(*page_model.split('.')) + Page = apps.get_model(*page_model.split(".")) self.queryset = Page.objects.active() def items(self): @@ -60,7 +69,7 @@ def items(self): if callable(base_qs): base_qs = base_qs() - self.max_depth = base_qs.aggregate(Max('level'))['level__max'] or 0 + self.max_depth = base_qs.aggregate(Max("level"))["level__max"] or 0 if self.depth_cutoff > 0: self.max_depth = min(self.depth_cutoff, self.max_depth) @@ -78,14 +87,13 @@ def items(self): for idx, page in enumerate(pages): if self.depth_cutoff > 0 and page.level == self.max_depth: continue - if getattr(page, 'navigation_extension', None): + if getattr(page, "navigation_extension", None): cnt = 0 for p in page.extended_navigation(): depth_too_deep = ( - self.depth_cutoff > 0 and - p.level > self.depth_cutoff) - not_in_nav = ( - self.navigation_only and not p.in_navigation) + self.depth_cutoff > 0 and p.level > self.depth_cutoff + ) + not_in_nav = self.navigation_only and not p.in_navigation if depth_too_deep or not_in_nav: continue cnt += 1 @@ -97,7 +105,7 @@ def items(self): return pages def lastmod(self, obj): - return getattr(obj, 'modification_date', None) + return getattr(obj, "modification_date", None) # the priority is computed of the depth in the tree of a page # may we should make an extension to give control to the user for priority @@ -107,7 +115,7 @@ def priority(self, obj): the site. Top level get highest priority, then each level is decreased by per_level. """ - if getattr(obj, 'override_url', '') == '/': + if getattr(obj, "override_url", "") == "/": prio = 1.0 else: prio = 1.0 - (obj.level + 1) * self.per_level @@ -119,4 +127,5 @@ def priority(self, obj): return "%0.2g" % min(1.0, prio) + # ------------------------------------------------------------------------ diff --git a/feincms/shortcuts.py b/feincms/shortcuts.py index 936460915..a24be3fe5 100644 --- a/feincms/shortcuts.py +++ b/feincms/shortcuts.py @@ -11,6 +11,6 @@ def render_to_response_best_match(request, template_name, dictionary=None): """ dictionary = dictionary or {} - dictionary['feincms_page'] = Page.objects.best_match_for_request(request) + dictionary["feincms_page"] = Page.objects.best_match_for_request(request) return render(request, template_name, dictionary) diff --git a/feincms/templatetags/applicationcontent_tags.py b/feincms/templatetags/applicationcontent_tags.py index 531e94eda..37dd2aae2 100644 --- a/feincms/templatetags/applicationcontent_tags.py +++ b/feincms/templatetags/applicationcontent_tags.py @@ -4,6 +4,7 @@ from django.template import TemplateSyntaxError from django.template.defaulttags import kwarg_re from django.utils.encoding import smart_str + try: from django.urls import NoReverseMatch except ImportError: @@ -11,9 +12,9 @@ from feincms.apps import ApplicationContent, app_reverse as do_app_reverse from feincms.templatetags.feincms_tags import _render_content + # backwards compatibility import -from feincms.templatetags.fragment_tags import ( - fragment, get_fragment, has_fragment) +from feincms.templatetags.fragment_tags import fragment, get_fragment, has_fragment register = template.Library() @@ -37,10 +38,11 @@ def feincms_render_region_appcontent(page, region, request): {% feincms_render_region_appcontent feincms_page "main" request %} {% endif %} """ - return ''.join( + return "".join( _render_content(content, request=request) for content in page.content.all_of_type(ApplicationContent) - if content.region == region) + if content.region == region + ) def _current_app(context): @@ -50,7 +52,7 @@ def _current_app(context): try: return context.request.resolver_match.namespace except AttributeError: - return getattr(context, 'current_app', None) + return getattr(context, "current_app", None) class AppReverseNode(template.Node): @@ -63,24 +65,31 @@ def __init__(self, view_name, urlconf, args, kwargs, asvar): def render(self, context): args = [arg.resolve(context) for arg in self.args] - kwargs = dict([ - (smart_str(k, 'ascii'), v.resolve(context)) - for k, v in self.kwargs.items()]) + kwargs = dict( + [ + (smart_str(k, "ascii"), v.resolve(context)) + for k, v in self.kwargs.items() + ] + ) view_name = self.view_name.resolve(context) urlconf = self.urlconf.resolve(context) try: url = do_app_reverse( - view_name, urlconf, args=args, kwargs=kwargs, - current_app=_current_app(context)) + view_name, + urlconf, + args=args, + kwargs=kwargs, + current_app=_current_app(context), + ) except NoReverseMatch: if self.asvar is None: raise - url = '' + url = "" if self.asvar: context[self.asvar] = url - return '' + return "" else: return url @@ -118,14 +127,15 @@ def app_reverse(parser, token): if len(bits) < 3: raise TemplateSyntaxError( "'%s' takes at least two arguments" - " (path to a view and a urlconf)" % bits[0]) + " (path to a view and a urlconf)" % bits[0] + ) viewname = parser.compile_filter(bits[1]) urlconf = parser.compile_filter(bits[2]) args = [] kwargs = {} asvar = None bits = bits[3:] - if len(bits) >= 2 and bits[-2] == 'as': + if len(bits) >= 2 and bits[-2] == "as": asvar = bits[-1] bits = bits[:-2] @@ -133,8 +143,7 @@ def app_reverse(parser, token): for bit in bits: match = kwarg_re.match(bit) if not match: - raise TemplateSyntaxError( - "Malformed arguments to app_reverse tag") + raise TemplateSyntaxError("Malformed arguments to app_reverse tag") name, value = match.groups() if name: kwargs[name] = parser.compile_filter(value) diff --git a/feincms/templatetags/feincms_admin_tags.py b/feincms/templatetags/feincms_admin_tags.py index 5292b410f..e8127eb39 100644 --- a/feincms/templatetags/feincms_admin_tags.py +++ b/feincms/templatetags/feincms_admin_tags.py @@ -23,7 +23,7 @@ def post_process_fieldsets(context, fieldset): return fieldset fields_to_include = set(fieldset.form.fields.keys()) - for f in ('id', 'DELETE', 'ORDER'): + for f in ("id", "DELETE", "ORDER"): fields_to_include.discard(f) def _filter_recursive(fields): @@ -46,37 +46,38 @@ def _filter_recursive(fields): for f in fields_to_include: new_fields.append(f) - if context.get('request'): - new_fields.extend(list( - fieldset.model_admin.get_readonly_fields( - context.get('request'), - context.get('original'), + if context.get("request"): + new_fields.extend( + list( + fieldset.model_admin.get_readonly_fields( + context.get("request"), context.get("original") + ) ) - )) + ) fieldset.fields = new_fields - return '' + return "" -@register.inclusion_tag('admin/feincms/content_type_selection_widget.html', - takes_context=True) +@register.inclusion_tag( + "admin/feincms/content_type_selection_widget.html", takes_context=True +) def show_content_type_selection_widget(context, region): """ {% show_content_type_selection_widget region %} """ - user = context['request'].user + user = context["request"].user types = OrderedDict({None: []}) for ct in region._content_types: # Skip cts that we shouldn't be adding anyway opts = ct._meta - perm = opts.app_label + "." + get_permission_codename('add', opts) + perm = opts.app_label + "." + get_permission_codename("add", opts) if not user.has_perm(perm): continue - types.setdefault( - getattr(ct, 'optgroup', None), - [], - ).append((ct.__name__.lower, ct._meta.verbose_name)) + types.setdefault(getattr(ct, "optgroup", None), []).append( + (ct.__name__.lower, ct._meta.verbose_name) + ) - return {'types': types} + return {"types": types} diff --git a/feincms/templatetags/feincms_page_tags.py b/feincms/templatetags/feincms_page_tags.py index 2ea6cd7c1..60eca87c8 100644 --- a/feincms/templatetags/feincms_page_tags.py +++ b/feincms/templatetags/feincms_page_tags.py @@ -18,21 +18,20 @@ from feincms.module.page.extensions.navigation import PagePretender from feincms.utils.templatetags import ( SimpleAssignmentNodeWithVarAndArgs, - do_simple_assignment_node_with_var_and_args_helper) + do_simple_assignment_node_with_var_and_args_helper, +) -logger = logging.getLogger('feincms.templatetags.page') +logger = logging.getLogger("feincms.templatetags.page") register = template.Library() assignment_tag = ( - register.simple_tag if django.VERSION >= (1, 9) - else register.assignment_tag + register.simple_tag if django.VERSION >= (1, 9) else register.assignment_tag ) def _get_page_model(): - return apps.get_model( - *feincms_settings.FEINCMS_DEFAULT_PAGE_MODEL.split('.')) + return apps.get_model(*feincms_settings.FEINCMS_DEFAULT_PAGE_MODEL.split(".")) # ------------------------------------------------------------------------ @@ -56,8 +55,7 @@ def feincms_nav(context, feincms_page, level=1, depth=1, group=None): if isinstance(feincms_page, HttpRequest): try: - feincms_page = page_class.objects.for_request( - feincms_page, best_match=True) + feincms_page = page_class.objects.for_request(feincms_page, best_match=True) except page_class.DoesNotExist: return [] @@ -68,8 +66,8 @@ def feincms_nav(context, feincms_page, level=1, depth=1, group=None): queryset = feincms_page.__class__._default_manager.in_navigation().filter( **{ - '%s__gte' % mptt_opts.level_attr: mptt_level_range[0], - '%s__lt' % mptt_opts.level_attr: mptt_level_range[1], + "%s__gte" % mptt_opts.level_attr: mptt_level_range[0], + "%s__lt" % mptt_opts.level_attr: mptt_level_range[1], } ) @@ -98,10 +96,13 @@ def feincms_nav(context, feincms_page, level=1, depth=1, group=None): queryset = page_class.objects.none() if parent: - if getattr(parent, 'navigation_extension', None): + if getattr(parent, "navigation_extension", None): # Special case for navigation extensions - return list(parent.extended_navigation( - depth=depth, request=context.get('request'))) + return list( + parent.extended_navigation( + depth=depth, request=context.get("request") + ) + ) # Apply descendant filter queryset &= parent.get_descendants() @@ -126,40 +127,44 @@ def _parentactive_filter(iterable): # navigationgroups extension support def _navigationgroup_filter(iterable): for elem in iterable: - if getattr(elem, 'navigation_group', None) == group: + if getattr(elem, "navigation_group", None) == group: yield elem queryset = _navigationgroup_filter(queryset) - if hasattr(feincms_page, 'navigation_extension'): + if hasattr(feincms_page, "navigation_extension"): # Filter out children of nodes which have a navigation extension def _navext_filter(iterable): current_navextension_node = None for elem in iterable: # Eliminate all subitems of last processed nav extension - if current_navextension_node is not None and \ - current_navextension_node.is_ancestor_of(elem): + if ( + current_navextension_node is not None + and current_navextension_node.is_ancestor_of(elem) + ): continue yield elem - if getattr(elem, 'navigation_extension', None): + if getattr(elem, "navigation_extension", None): current_navextension_node = elem try: for extended in elem.extended_navigation( - depth=depth, request=context.get('request')): + depth=depth, request=context.get("request") + ): # Only return items from the extended navigation # which are inside the requested level+depth # values. The "-1" accounts for the differences in # MPTT and navigation level counting - this_level = getattr( - extended, mptt_opts.level_attr, 0) + this_level = getattr(extended, mptt_opts.level_attr, 0) if this_level < level + depth - 1: yield extended except Exception as e: logger.warn( "feincms_nav caught exception in navigation" " extension for page %d: %s", - current_navextension_node.id, format_exception(e)) + current_navextension_node.id, + format_exception(e), + ) else: current_navextension_node = None @@ -200,21 +205,19 @@ class LanguageLinksNode(SimpleAssignmentNodeWithVarAndArgs): """ def what(self, page, args): - only_existing = args.get('existing', False) - exclude_current = args.get('excludecurrent', False) + only_existing = args.get("existing", False) + exclude_current = args.get("excludecurrent", False) # Preserve the trailing path when switching languages if extra_path # exists (this is mostly the case when we are working inside an # ApplicationContent-managed page subtree) - trailing_path = '' - request = args.get('request', None) + trailing_path = "" + request = args.get("request", None) if request: # Trailing path without first slash - trailing_path = request._feincms_extra_context.get( - 'extra_path', '')[1:] + trailing_path = request._feincms_extra_context.get("extra_path", "")[1:] - translations = dict( - (t.language, t) for t in page.available_translations()) + translations = dict((t.language, t) for t in page.available_translations()) translations[page.language] = page links = [] @@ -224,10 +227,9 @@ def what(self, page, args): # hardcoded paths... bleh if key in translations: - links.append(( - key, - name, - translations[key].get_absolute_url() + trailing_path)) + links.append( + (key, name, translations[key].get_absolute_url() + trailing_path) + ) elif not only_existing: links.append((key, name, None)) @@ -235,8 +237,9 @@ def what(self, page, args): register.tag( - 'feincms_languagelinks', - do_simple_assignment_node_with_var_and_args_helper(LanguageLinksNode)) + "feincms_languagelinks", + do_simple_assignment_node_with_var_and_args_helper(LanguageLinksNode), +) # ------------------------------------------------------------------------ @@ -251,14 +254,13 @@ def _translate_page_into(page, language, default=None): return page if language is not None: - translations = dict( - (t.language, t) for t in page.available_translations()) + translations = dict((t.language, t) for t in page.available_translations()) if language in translations: return translations[language] except AttributeError: pass - if hasattr(default, '__call__'): + if hasattr(default, "__call__"): return default(page=page) return default @@ -284,16 +286,16 @@ class TranslatedPageNode(SimpleAssignmentNodeWithVarAndArgs): whether settings LANGUAGES contains that code -- so naming a variable "en" will probably not do what is intended. """ + def what(self, page, args, default=None): - language = args.get('language', None) + language = args.get("language", None) if language is None: language = settings.LANGUAGES[0][0] else: if language not in (x[0] for x in settings.LANGUAGES): try: - language = template.Variable(language).resolve( - self.render_context) + language = template.Variable(language).resolve(self.render_context) except template.VariableDoesNotExist: language = settings.LANGUAGES[0][0] @@ -301,32 +303,34 @@ def what(self, page, args, default=None): register.tag( - 'feincms_translatedpage', - do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNode)) + "feincms_translatedpage", + do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNode), +) # ------------------------------------------------------------------------ class TranslatedPageNodeOrBase(TranslatedPageNode): def what(self, page, args): return super(TranslatedPageNodeOrBase, self).what( - page, args, - default=getattr(page, 'get_original_translation', page)) + page, args, default=getattr(page, "get_original_translation", page) + ) register.tag( - 'feincms_translatedpage_or_base', - do_simple_assignment_node_with_var_and_args_helper( - TranslatedPageNodeOrBase)) + "feincms_translatedpage_or_base", + do_simple_assignment_node_with_var_and_args_helper(TranslatedPageNodeOrBase), +) # ------------------------------------------------------------------------ @register.filter def feincms_translated_or_base(pages, language=None): - if not hasattr(pages, '__iter__'): + if not hasattr(pages, "__iter__"): pages = [pages] for page in pages: yield _translate_page_into( - page, language, default=page.get_original_translation) + page, language, default=page.get_original_translation + ) # ------------------------------------------------------------------------ @@ -443,8 +447,10 @@ def siblings_along_path_to(page_list, page2): # comes from feincms_nav). We'll cope with the fall-out of that # assumption when it happens... ancestors = [ - a_page for a_page in page_list - if a_page.is_ancestor_of(page2, include_self=True)] + a_page + for a_page in page_list + if a_page.is_ancestor_of(page2, include_self=True) + ] top_level = min((a_page.level for a_page in page_list)) if not ancestors: @@ -454,25 +460,25 @@ def siblings_along_path_to(page_list, page2): page_class = _get_page_model() p = page_class( - title="dummy", - tree_id=-1, - parent_id=None, - in_navigation=False) + title="dummy", tree_id=-1, parent_id=None, in_navigation=False + ) ancestors = (p,) siblings = [ - a_page for a_page in page_list if ( - a_page.parent_id == page2.id or - a_page.level == top_level or - any((_is_sibling_of(a_page, a) for a in ancestors)) + a_page + for a_page in page_list + if ( + a_page.parent_id == page2.id + or a_page.level == top_level + or any((_is_sibling_of(a_page, a) for a in ancestors)) ) ] return siblings except (AttributeError, ValueError) as e: logger.warn( - "siblings_along_path_to caught exception: %s", - format_exception(e)) + "siblings_along_path_to caught exception: %s", format_exception(e) + ) return () @@ -495,25 +501,25 @@ def page_is_active(context, page, feincms_page=None, path=None): """ if isinstance(page, PagePretender): if path is None: - path = context['request'].path_info + path = context["request"].path_info return path.startswith(page.get_absolute_url()) else: if feincms_page is None: - feincms_page = context['feincms_page'] + feincms_page = context["feincms_page"] return page.is_ancestor_of(feincms_page, include_self=True) # ------------------------------------------------------------------------ @register.simple_tag def feincms_parentlink(of_, feincms_page, **kwargs): - level = int(kwargs.get('level', 1)) + level = int(kwargs.get("level", 1)) if feincms_page.level + 1 == level: return feincms_page.get_absolute_url() elif feincms_page.level + 1 < level: - return '#' + return "#" try: return feincms_page.get_ancestors()[level - 1].get_absolute_url() except IndexError: - return '#' + return "#" diff --git a/feincms/templatetags/feincms_tags.py b/feincms/templatetags/feincms_tags.py index 1450d7ace..faeed6c57 100644 --- a/feincms/templatetags/feincms_tags.py +++ b/feincms/templatetags/feincms_tags.py @@ -16,8 +16,7 @@ register = template.Library() assignment_tag = ( - register.simple_tag if django.VERSION >= (1, 9) - else register.assignment_tag + register.simple_tag if django.VERSION >= (1, 9) else register.assignment_tag ) @@ -25,15 +24,15 @@ def _render_content(content, **kwargs): # Track current render level and abort if we nest too deep. Avoids # crashing in recursive page contents (eg. a page list that contains # itself or similar). - request = kwargs.get('request') + request = kwargs.get("request") if request is not None: - level = getattr(request, 'feincms_render_level', 0) + level = getattr(request, "feincms_render_level", 0) if level > 10: - logging.getLogger('feincms').error( - 'Refusing to render %r, render level is already %s' % ( - content, level)) + logging.getLogger("feincms").error( + "Refusing to render %r, render level is already %s" % (content, level) + ) return - setattr(request, 'feincms_render_level', level + 1) + setattr(request, "feincms_render_level", level + 1) r = content.render(**kwargs) @@ -62,8 +61,8 @@ def _render_content(content, **kwargs): return plugin_template.render(context) if request is not None: - level = getattr(request, 'feincms_render_level', 1) - setattr(request, 'feincms_render_level', max(level - 1, 0)) + level = getattr(request, "feincms_render_level", 1) + setattr(request, "feincms_render_level", max(level - 1, 0)) return r @@ -74,11 +73,14 @@ def feincms_render_region(context, feincms_object, region, request=None): {% feincms_render_region feincms_page "main" request %} """ if not feincms_object: - return '' + return "" - return mark_safe(''.join( - _render_content(content, request=request, context=context) - for content in getattr(feincms_object.content, region))) + return mark_safe( + "".join( + _render_content(content, request=request, context=context) + for content in getattr(feincms_object.content, region) + ) + ) @register.simple_tag(takes_context=True) @@ -87,7 +89,7 @@ def feincms_render_content(context, content, request=None): {% feincms_render_content content request %} """ if not content: - return '' + return "" return _render_content(content, request=request, context=context) diff --git a/feincms/templatetags/feincms_thumbnail.py b/feincms/templatetags/feincms_thumbnail.py index 09f397746..299889a74 100644 --- a/feincms/templatetags/feincms_thumbnail.py +++ b/feincms/templatetags/feincms_thumbnail.py @@ -19,16 +19,16 @@ from feincms import settings -logger = logging.getLogger('feincms.templatetags.thumbnail') +logger = logging.getLogger("feincms.templatetags.thumbnail") register = template.Library() @python_2_unicode_compatible class Thumbnailer(object): - THUMBNAIL_SIZE_RE = re.compile(r'^(?P\d+)x(?P\d+)$') - MARKER = '_thumb_' + THUMBNAIL_SIZE_RE = re.compile(r"^(?P\d+)x(?P\d+)$") + MARKER = "_thumb_" - def __init__(self, filename, size='200x200'): + def __init__(self, filename, size="200x200"): self.filename = filename self.size = size @@ -39,39 +39,41 @@ def url(self): def __str__(self): match = self.THUMBNAIL_SIZE_RE.match(self.size) if not (self.filename and match): - return '' + return "" matches = match.groupdict() # figure out storage - if hasattr(self.filename, 'storage'): + if hasattr(self.filename, "storage"): storage = self.filename.storage else: storage = default_storage # figure out name - if hasattr(self.filename, 'name'): + if hasattr(self.filename, "name"): filename = self.filename.name else: filename = force_text(self.filename) # defining the filename and the miniature filename try: - basename, format = filename.rsplit('.', 1) + basename, format = filename.rsplit(".", 1) except ValueError: - basename, format = filename, 'jpg' - - miniature = ''.join([ - settings.FEINCMS_THUMBNAIL_DIR, - basename, - self.MARKER, - self.size, - '.', - format, - ]) + basename, format = filename, "jpg" + + miniature = "".join( + [ + settings.FEINCMS_THUMBNAIL_DIR, + basename, + self.MARKER, + self.size, + ".", + format, + ] + ) if settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT != 0: - cache_key = 'thumb_url_%s' % miniature + cache_key = "thumb_url_%s" % miniature url = cache.get(cache_key) if url: return url @@ -80,15 +82,15 @@ def __str__(self): generate = True else: try: - generate = ( - storage.modified_time(miniature) < - storage.modified_time(filename)) + generate = storage.modified_time(miniature) < storage.modified_time( + filename + ) except (NotImplementedError, AttributeError): # storage does NOT support modified_time generate = False except (OSError, IOError): # Someone might have delete the file - return '' + return "" if generate: try: @@ -96,13 +98,15 @@ def __str__(self): storage=storage, original=filename, size=matches, - miniature=miniature) + miniature=miniature, + ) except Exception as exc: logger.warning( - 'Rendering a thumbnail failed: %r', + "Rendering a thumbnail failed: %r", exc, exc_info=True, - extra={'stack': True, 'exception': exc}) + extra={"stack": True, "exception": exc}, + ) # PIL raises a plethora of Exceptions if reading the image # is not possible. Since we cannot be sure what Exception will # happen, catch them all so the thumbnailer will never fail. @@ -110,11 +114,7 @@ def __str__(self): url = storage.url(miniature) if settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT != 0: - cache.set( - cache_key, - url, - timeout=settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT - ) + cache.set(cache_key, url, timeout=settings.FEINCMS_THUMBNAIL_CACHE_TIMEOUT) return url def generate(self, storage, original, size, miniature): @@ -123,15 +123,15 @@ def generate(self, storage, original, size, miniature): image = Image.open(original_bytes) # defining the size - w, h = int(size['w']), int(size['h']) + w, h = int(size["w"]), int(size["h"]) format = image.format # Save format for the save() call later image.thumbnail([w, h], Image.ANTIALIAS) buf = BytesIO() - if image.mode not in ('RGBA', 'RGB', 'L'): - image = image.convert('RGBA') - if format.lower() not in ('jpg', 'jpeg', 'png'): - format = 'jpeg' + if image.mode not in ("RGBA", "RGB", "L"): + image = image.convert("RGBA") + if format.lower() not in ("jpg", "jpeg", "png"): + format = "jpeg" image.save(buf, format, quality=90) raw_data = buf.getvalue() buf.close() @@ -143,19 +143,18 @@ def generate(self, storage, original, size, miniature): class CropscaleThumbnailer(Thumbnailer): - THUMBNAIL_SIZE_RE = re.compile( - r'^(?P\d+)x(?P\d+)(-(?P\d+)x(?P\d+))?$') - MARKER = '_cropscale_' + THUMBNAIL_SIZE_RE = re.compile(r"^(?P\d+)x(?P\d+)(-(?P\d+)x(?P\d+))?$") + MARKER = "_cropscale_" def generate(self, storage, original, size, miniature): with storage.open(original) as original_handle: with BytesIO(original_handle.read()) as original_bytes: image = Image.open(original_bytes) - w, h = int(size['w']), int(size['h']) + w, h = int(size["w"]), int(size["h"]) - if size['x'] and size['y']: - x, y = int(size['x']), int(size['y']) + if size["x"] and size["y"]: + x, y = int(size["x"]), int(size["y"]) else: x, y = 50, 50 @@ -176,18 +175,21 @@ def generate(self, storage, original, size, miniature): y_offset = int(float(src_height - crop_height) * y / 100) format = image.format # Save format for the save() call later - image = image.crop(( - x_offset, - y_offset, - x_offset + int(crop_width), - y_offset + int(crop_height))) + image = image.crop( + ( + x_offset, + y_offset, + x_offset + int(crop_width), + y_offset + int(crop_height), + ) + ) image = image.resize((dst_width, dst_height), Image.ANTIALIAS) buf = BytesIO() - if image.mode not in ('RGBA', 'RGB', 'L'): - image = image.convert('RGBA') - if format.lower() not in ('jpg', 'jpeg', 'png'): - format = 'jpeg' + if image.mode not in ("RGBA", "RGB", "L"): + image = image.convert("RGBA") + if format.lower() not in ("jpg", "jpeg", "png"): + format = "jpeg" image.save(buf, format, quality=90) raw_data = buf.getvalue() buf.close() @@ -199,7 +201,7 @@ def generate(self, storage, original, size, miniature): @register.filter -def thumbnail(filename, size='200x200'): +def thumbnail(filename, size="200x200"): """ Creates a thumbnail from the image passed, returning its path:: @@ -224,7 +226,7 @@ def thumbnail(filename, size='200x200'): @register.filter -def cropscale(filename, size='200x200'): +def cropscale(filename, size="200x200"): """ Scales the image down and crops it so that its size equals exactly the size passed (as long as the initial image is bigger than the specification). diff --git a/feincms/templatetags/fragment_tags.py b/feincms/templatetags/fragment_tags.py index 5c5e7098b..fd8fc4904 100644 --- a/feincms/templatetags/fragment_tags.py +++ b/feincms/templatetags/fragment_tags.py @@ -7,7 +7,7 @@ class FragmentNode(template.Node): - def __init__(self, nodelist, request, identifier, mode='append'): + def __init__(self, nodelist, request, identifier, mode="append"): self.nodelist = nodelist self.request_var = template.Variable(request) self.identifier_var = template.Variable(identifier) @@ -18,19 +18,19 @@ def render(self, context): identifier = self.identifier_var.resolve(context) rendered = self.nodelist.render(context) - if not hasattr(request, '_feincms_fragments'): + if not hasattr(request, "_feincms_fragments"): request._feincms_fragments = {} - old = request._feincms_fragments.get(identifier, '') + old = request._feincms_fragments.get(identifier, "") - if self.mode == 'prepend': + if self.mode == "prepend": request._feincms_fragments[identifier] = rendered + old - elif self.mode == 'replace': + elif self.mode == "replace": request._feincms_fragments[identifier] = rendered else: # append request._feincms_fragments[identifier] = old + rendered - return '' + return "" @register.tag @@ -50,7 +50,7 @@ def fragment(parser, token): {% endfragment %} """ - nodelist = parser.parse(('endfragment'),) + nodelist = parser.parse(("endfragment")) parser.delete_first_token() return FragmentNode(nodelist, *token.contents.split()[1:]) @@ -69,11 +69,11 @@ def render(self, context): try: value = request._feincms_fragments[fragment] except (AttributeError, KeyError): - value = '' + value = "" if self.as_var: context[self.as_var] = value - return '' + return "" return value @@ -95,10 +95,11 @@ def get_fragment(parser, token): if len(fragments) == 3: return GetFragmentNode(fragments[1], fragments[2]) - elif len(fragments) == 5 and fragments[3] == 'as': + elif len(fragments) == 5 and fragments[3] == "as": return GetFragmentNode(fragments[1], fragments[2], fragments[4]) raise template.TemplateSyntaxError( - 'Invalid syntax for get_fragment: %s' % token.contents) + "Invalid syntax for get_fragment: %s" % token.contents + ) @register.filter @@ -108,4 +109,4 @@ def has_fragment(request, identifier): {% if request|has_fragment:"title" %} ... {% endif %} """ - return getattr(request, '_feincms_fragments', {}).get(identifier) + return getattr(request, "_feincms_fragments", {}).get(identifier) diff --git a/feincms/translations.py b/feincms/translations.py index eaab23da9..68e3a2cf8 100644 --- a/feincms/translations.py +++ b/feincms/translations.py @@ -47,6 +47,7 @@ class _NoTranslation(object): """Simple marker for when no translations exist for a certain object Only used for caching.""" + pass @@ -65,7 +66,7 @@ def short_language_code(code=None): if code is None: code = translation.get_language() - pos = code.find('-') + pos = code.find("-") if pos > -1: return code[:pos] return code @@ -91,6 +92,7 @@ def lookup_translations(language_code=None): The current language is used if ``language_code`` isn't specified. """ + def _transform(qs): lang_ = language_code if language_code else translation.get_language() @@ -112,22 +114,16 @@ def _transform(qs): if not instance_dict: return - candidates = list( - instance_dict.values() - )[0].translations.model._default_manager.all() + candidates = list(instance_dict.values())[ + 0 + ].translations.model._default_manager.all() if instance_dict: - _process(candidates, instance_dict, lang_, 'iexact') + _process(candidates, instance_dict, lang_, "iexact") if instance_dict: - _process( - candidates, - instance_dict, - settings.LANGUAGE_CODE, - 'istartswith', - ) + _process(candidates, instance_dict, settings.LANGUAGE_CODE, "istartswith") if instance_dict: - for candidate in candidates.filter( - parent__pk__in=instance_dict.keys()): + for candidate in candidates.filter(parent__pk__in=instance_dict.keys()): if candidate.parent_id in instance_dict: _found(instance_dict, candidate) @@ -145,9 +141,9 @@ def _found(instance_dict, candidate): def _process(candidates, instance_dict, lang_, op_): candidates = candidates.filter( Q(parent__pk__in=instance_dict.keys()), - Q(**{'language_code__' + op_: lang_}) | - Q(**{'language_code__' + op_: short_language_code(lang_)}) - ).order_by('-language_code') + Q(**{"language_code__" + op_: lang_}) + | Q(**{"language_code__" + op_: short_language_code(lang_)}), + ).order_by("-language_code") for candidate in candidates: # The candidate's parent might already have a translation by now @@ -169,9 +165,9 @@ def only_language(self, language=short_language_code): Uses the currently active language by default. """ - return self.filter(translations__language_code=( - language() if callable(language) else language - )) + return self.filter( + translations__language_code=(language() if callable(language) else language) + ) @python_2_unicode_compatible @@ -183,16 +179,19 @@ class TranslatedObjectMixin(object): def _get_translation_object(self, queryset, language_code): try: return queryset.filter( - Q(language_code__iexact=language_code) | - Q(language_code__iexact=short_language_code(language_code)) - ).order_by('-language_code')[0] + Q(language_code__iexact=language_code) + | Q(language_code__iexact=short_language_code(language_code)) + ).order_by("-language_code")[0] except IndexError: try: return queryset.filter( - Q(language_code__istartswith=settings.LANGUAGE_CODE) | - Q(language_code__istartswith=short_language_code( - settings.LANGUAGE_CODE)) - ).order_by('-language_code')[0] + Q(language_code__istartswith=settings.LANGUAGE_CODE) + | Q( + language_code__istartswith=short_language_code( + settings.LANGUAGE_CODE + ) + ) + ).order_by("-language_code")[0] except IndexError: try: return queryset.all()[0] @@ -204,15 +203,8 @@ def get_translation_cache_key(self, language_code=None): can purge on-demand""" if not language_code: language_code = translation.get_language() - return ( - ('FEINCMS:%d:XLATION:' % getattr(settings, 'SITE_ID', 0)) + - '-'.join( - ['%s' % s for s in ( - self._meta.db_table, - self.id, - language_code, - )] - ) + return ("FEINCMS:%d:XLATION:" % getattr(settings, "SITE_ID", 0)) + "-".join( + ["%s" % s for s in (self._meta.db_table, self.id, language_code)] ) def get_translation(self, language_code=None): @@ -226,7 +218,8 @@ def get_translation(self, language_code=None): if trans is None: try: trans = self._get_translation_object( - self.translations.all(), language_code) + self.translations.all(), language_code + ) except ObjectDoesNotExist: trans = _NoTranslation cache.set(key, trans) @@ -240,13 +233,13 @@ def get_translation(self, language_code=None): @property def translation(self): - if not hasattr(self, '_cached_translation'): + if not hasattr(self, "_cached_translation"): self._cached_translation = self.get_translation() return self._cached_translation @property def available_translations(self): - return self.translations.values_list('language_code', flat=True) + return self.translations.values_list("language_code", flat=True) def __str__(self): try: @@ -255,7 +248,7 @@ def __str__(self): return self.__class__.__name__ if translation: - return '%s' % translation + return "%s" % translation return self.__class__.__name__ @@ -280,14 +273,18 @@ def Translation(model): class Inner(models.Model): parent = models.ForeignKey( - model, related_name='translations', on_delete=models.CASCADE) + model, related_name="translations", on_delete=models.CASCADE + ) language_code = models.CharField( - _('language'), max_length=10, - choices=settings.LANGUAGES, default=settings.LANGUAGES[0][0], - editable=len(settings.LANGUAGES) > 1) + _("language"), + max_length=10, + choices=settings.LANGUAGES, + default=settings.LANGUAGES[0][0], + editable=len(settings.LANGUAGES) > 1, + ) class Meta: - unique_together = ('parent', 'language_code') + unique_together = ("parent", "language_code") # (beware the above will not be inherited automatically if you # provide a Meta class within your translation subclass) abstract = True @@ -298,11 +295,13 @@ def short_language_code(self): def save(self, *args, **kwargs): super(Inner, self).save(*args, **kwargs) self.parent.purge_translation_cache() + save.alters_data = True def delete(self, *args, **kwargs): super(Inner, self).delete(*args, **kwargs) self.parent.purge_translation_cache() + delete.alters_data = True return Inner @@ -322,8 +321,7 @@ def admin_translationinline(model, inline_class=admin.StackedInline, **kwargs): ) """ - kwargs['extra'] = 1 - kwargs['max_num'] = len(settings.LANGUAGES) - kwargs['model'] = model - return type( - str(model.__class__.__name__ + 'Inline'), (inline_class,), kwargs) + kwargs["extra"] = 1 + kwargs["max_num"] = len(settings.LANGUAGES) + kwargs["model"] = model + return type(str(model.__class__.__name__ + "Inline"), (inline_class,), kwargs) diff --git a/feincms/urls.py b/feincms/urls.py index 357f72e9b..bb7bd851b 100644 --- a/feincms/urls.py +++ b/feincms/urls.py @@ -8,6 +8,6 @@ handler = Handler.as_view() urlpatterns = [ - url(r'^$', handler, name='feincms_home'), - url(r'^(.*)/$', handler, name='feincms_handler'), + url(r"^$", handler, name="feincms_home"), + url(r"^(.*)/$", handler, name="feincms_handler"), ] diff --git a/feincms/utils/__init__.py b/feincms/utils/__init__.py index aef8b207b..75397f11f 100644 --- a/feincms/utils/__init__.py +++ b/feincms/utils/__init__.py @@ -27,8 +27,8 @@ def get_object(path, fail_silently=False): return import_module(path) except ImportError: try: - dot = path.rindex('.') - mod, fn = path[:dot], path[dot + 1:] + dot = path.rindex(".") + mod, fn = path[:dot], path[dot + 1 :] return getattr(import_module(mod), fn) except (AttributeError, ImportError): @@ -60,8 +60,7 @@ def get_model_instance(app_label, model_name, pk): # ------------------------------------------------------------------------ -REDIRECT_TO_RE = re.compile( - r'^(?P\w+).(?P\w+):(?P\d+)$') +REDIRECT_TO_RE = re.compile(r"^(?P\w+).(?P\w+):(?P\d+)$") def match_model_string(s): @@ -77,7 +76,7 @@ def match_model_string(s): if not match: return None matches = match.groupdict() - return (matches['app_label'], matches['model_name'], int(matches['pk'])) + return (matches["app_label"], matches["model_name"], int(matches["pk"])) # ------------------------------------------------------------------------ @@ -89,14 +88,17 @@ def copy_model_instance(obj, exclude=None): exclude = exclude or () initial = dict( - (f.name, getattr(obj, f.name)) for f in obj._meta.fields - if not isinstance(f, AutoField) and f.name not in exclude and - f not in obj._meta.parents.values()) + (f.name, getattr(obj, f.name)) + for f in obj._meta.fields + if not isinstance(f, AutoField) + and f.name not in exclude + and f not in obj._meta.parents.values() + ) return obj.__class__(**initial) # ------------------------------------------------------------------------ -def shorten_string(str, max_length=50, ellipsis=' … '): +def shorten_string(str, max_length=50, ellipsis=" … "): """ Shorten a string for display, truncate it intelligently when too long. Try to cut it in 2/3 + ellipsis + 1/3 of the original title. Also try to @@ -105,14 +107,14 @@ def shorten_string(str, max_length=50, ellipsis=' … '): if len(str) >= max_length: first_part = int(max_length * 0.6) - next_space = str[first_part:(max_length // 2 - first_part)].find(' ') - if (next_space >= 0 and - first_part + next_space + len(ellipsis) < max_length): + next_space = str[first_part : (max_length // 2 - first_part)].find(" ") + if next_space >= 0 and first_part + next_space + len(ellipsis) < max_length: first_part += next_space return ( - str[:first_part] + - ellipsis + - str[-(max_length - first_part - len(ellipsis)):]) + str[:first_part] + + ellipsis + + str[-(max_length - first_part - len(ellipsis)) :] + ) return str @@ -120,31 +122,22 @@ def shorten_string(str, max_length=50, ellipsis=' … '): def get_singleton(template_key, cls=None, raise_exception=True): cls = cls or settings.FEINCMS_DEFAULT_PAGE_MODEL try: - model = apps.get_model(*cls.split('.')) + model = apps.get_model(*cls.split(".")) if not model: raise ImproperlyConfigured('Cannot load model "%s"' % cls) try: assert model._feincms_templates[template_key].singleton except AttributeError as e: raise ImproperlyConfigured( - '%r does not seem to be a valid FeinCMS base class (%r)' % ( - model, - e, - ) + "%r does not seem to be a valid FeinCMS base class (%r)" % (model, e) ) except KeyError: raise ImproperlyConfigured( - '%r is not a registered template for %r!' % ( - template_key, - model, - ) + "%r is not a registered template for %r!" % (template_key, model) ) except AssertionError: raise ImproperlyConfigured( - '%r is not a *singleton* template for %r!' % ( - template_key, - model, - ) + "%r is not a *singleton* template for %r!" % (template_key, model) ) try: return model._default_manager.get(template_key=template_key) @@ -161,4 +154,4 @@ def get_singleton(template_key, cls=None, raise_exception=True): def get_singleton_url(template_key, cls=None, raise_exception=True): obj = get_singleton(template_key, cls, raise_exception) - return obj.get_absolute_url() if obj else '#broken-link' + return obj.get_absolute_url() if obj else "#broken-link" diff --git a/feincms/utils/queryset_transform.py b/feincms/utils/queryset_transform.py index e7110a7d1..7ded0c45b 100644 --- a/feincms/utils/queryset_transform.py +++ b/feincms/utils/queryset_transform.py @@ -91,7 +91,7 @@ class TransformQuerySet(models.query.QuerySet): def __init__(self, *args, **kwargs): super(TransformQuerySet, self).__init__(*args, **kwargs) self._transform_fns = [] - self._orig_iterable_class = getattr(self, '_iterable_class', None) + self._orig_iterable_class = getattr(self, "_iterable_class", None) def _clone(self, *args, **kwargs): c = super(TransformQuerySet, self)._clone(*args, **kwargs) @@ -104,13 +104,16 @@ def transform(self, *fn): return c if django.VERSION < (1, 11): + def iterator(self): result_iter = super(TransformQuerySet, self).iterator() if not self._transform_fns: return result_iter - if getattr(self, '_iterable_class', None) != self._orig_iterable_class: # noqa + if ( + getattr(self, "_iterable_class", None) != self._orig_iterable_class + ): # noqa # Do not process the result of values() and values_list() return result_iter @@ -120,17 +123,21 @@ def iterator(self): return iter(results) else: + def _fetch_all(self): super(TransformQuerySet, self)._fetch_all() - if getattr(self, '_iterable_class', None) == self._orig_iterable_class: # noqa + if ( + getattr(self, "_iterable_class", None) == self._orig_iterable_class + ): # noqa for fn in self._transform_fns: fn(self._result_cache) -if hasattr(models.Manager, 'from_queryset'): +if hasattr(models.Manager, "from_queryset"): TransformManager = models.Manager.from_queryset(TransformQuerySet) else: + class TransformManager(models.Manager): def get_queryset(self): return TransformQuerySet(self.model, using=self._db) diff --git a/feincms/utils/templatetags.py b/feincms/utils/templatetags.py index 6df5592c5..18258b98d 100644 --- a/feincms/utils/templatetags.py +++ b/feincms/utils/templatetags.py @@ -1,4 +1,4 @@ -''' +""" I really hate repeating myself. These are helpers that avoid typing the whole thing over and over when implementing additional template tags @@ -7,7 +7,7 @@ {% tag as var_name %} {% tag of template_var as var_name %} {% tag of template_var as var_name arg1,arg2,kwarg3=4 %} -''' +""" from __future__ import absolute_import, unicode_literals @@ -17,9 +17,9 @@ def _parse_args(argstr, context=None): try: args = {} - for token in argstr.split(','): - if '=' in token: - k, v = token.split('=', 1) + for token in argstr.split(","): + if "=" in token: + k, v = token.split("=", 1) if context: try: args[k] = template.Variable(v).resolve(context) @@ -33,23 +33,21 @@ def _parse_args(argstr, context=None): return args except TypeError: - raise template.TemplateSyntaxError('Malformed arguments') + raise template.TemplateSyntaxError("Malformed arguments") def do_simple_assignment_node_with_var_and_args_helper(cls): def _func(parser, token): try: - tag_name, of_, in_var_name, as_, var_name, args =\ - token.contents.split() + tag_name, of_, in_var_name, as_, var_name, args = token.contents.split() except ValueError: try: - tag_name, of_, in_var_name, as_, var_name =\ - token.contents.split() - args = '' + tag_name, of_, in_var_name, as_, var_name = token.contents.split() + args = "" except ValueError: raise template.TemplateSyntaxError( - 'Invalid syntax for %s node: %s' % ( - cls.__name__, token.contents)) + "Invalid syntax for %s node: %s" % (cls.__name__, token.contents) + ) return cls(tag_name, in_var_name, var_name, args) @@ -69,9 +67,8 @@ def render(self, context): instance = self.in_var.resolve(context) except template.VariableDoesNotExist: context[self.var_name] = [] - return '' + return "" - context[self.var_name] = self.what( - instance, _parse_args(self.args, context)) + context[self.var_name] = self.what(instance, _parse_args(self.args, context)) - return '' + return "" diff --git a/feincms/views/__init__.py b/feincms/views/__init__.py index 416ab850f..014a6adc8 100644 --- a/feincms/views/__init__.py +++ b/feincms/views/__init__.py @@ -15,19 +15,20 @@ class Handler(ContentView): page_model_path = None - context_object_name = 'feincms_page' + context_object_name = "feincms_page" @cached_property def page_model(self): model = self.page_model_path or settings.FEINCMS_DEFAULT_PAGE_MODEL - return apps.get_model(*model.split('.')) + return apps.get_model(*model.split(".")) def get_object(self): path = None if self.args: path = self.args[0] return self.page_model._default_manager.for_request( - self.request, raise404=True, best_match=True, path=path) + self.request, raise404=True, best_match=True, path=path + ) def dispatch(self, request, *args, **kwargs): try: @@ -36,7 +37,9 @@ def dispatch(self, request, *args, **kwargs): if settings.FEINCMS_CMS_404_PAGE is not None: logger.info( "Http404 raised for '%s', attempting redirect to" - " FEINCMS_CMS_404_PAGE", args[0]) + " FEINCMS_CMS_404_PAGE", + args[0], + ) try: # Fudge environment so that we end up resolving the right # page. @@ -45,12 +48,12 @@ def dispatch(self, request, *args, **kwargs): # page url. # Also clear out the _feincms_page attribute which caches # page lookups (and would just re-raise a 404). - request.path = request.path_info =\ - settings.FEINCMS_CMS_404_PAGE - if hasattr(request, '_feincms_page'): - delattr(request, '_feincms_page') + request.path = request.path_info = settings.FEINCMS_CMS_404_PAGE + if hasattr(request, "_feincms_page"): + delattr(request, "_feincms_page") response = super(Handler, self).dispatch( - request, settings.FEINCMS_CMS_404_PAGE, **kwargs) + request, settings.FEINCMS_CMS_404_PAGE, **kwargs + ) # Only set status if we actually have a page. If we get for # example a redirect, overwriting would yield a blank page if response.status_code == 200: @@ -58,9 +61,9 @@ def dispatch(self, request, *args, **kwargs): return response except Http404: logger.error( - "Http404 raised while resolving" - " FEINCMS_CMS_404_PAGE=%s", - settings.FEINCMS_CMS_404_PAGE) + "Http404 raised while resolving" " FEINCMS_CMS_404_PAGE=%s", + settings.FEINCMS_CMS_404_PAGE, + ) raise e else: raise diff --git a/feincms/views/decorators.py b/feincms/views/decorators.py index 2fab30127..656e50800 100644 --- a/feincms/views/decorators.py +++ b/feincms/views/decorators.py @@ -6,5 +6,7 @@ from feincms.apps import * warnings.warn( - 'Import ApplicationContent and friends from feincms.content.application.models', - DeprecationWarning, stacklevel=2) + "Import ApplicationContent and friends from feincms.content.application.models", + DeprecationWarning, + stacklevel=2, +) diff --git a/setup.cfg b/setup.cfg index cc4bd75fa..948dea0f3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,8 @@ [flake8] -exclude=venv,.tox,build,docs,migrations +exclude=venv,build,docs,.tox,migrations +ignore=E203,W503 +max-line-length=88 +# max-complexity=10 [wheel] universal = 1 diff --git a/setup.py b/setup.py index 2f1156902..a9fdce423 100755 --- a/setup.py +++ b/setup.py @@ -7,53 +7,51 @@ def read(filename): path = os.path.join(os.path.dirname(__file__), filename) - with open(path, encoding='utf-8') as handle: + with open(path, encoding="utf-8") as handle: return handle.read() -version = __import__('feincms').__version__ -devstatus = 'Development Status :: 5 - Production/Stable' -if '.dev' in version: - devstatus = 'Development Status :: 3 - Alpha' -elif '.pre' in version: - devstatus = 'Development Status :: 4 - Beta' +version = __import__("feincms").__version__ +devstatus = "Development Status :: 5 - Production/Stable" +if ".dev" in version: + devstatus = "Development Status :: 3 - Alpha" +elif ".pre" in version: + devstatus = "Development Status :: 4 - Beta" setup( - name='FeinCMS', + name="FeinCMS", version=version, - description='Django-based Page CMS and CMS building toolkit.', - long_description=read('README.rst'), - author='Matthias Kestenholz', - author_email='mk@feinheit.ch', - url='http://github.com/feincms/feincms/', - license='BSD License', - platforms=['OS Independent'], - packages=find_packages( - exclude=['tests'] - ), + description="Django-based Page CMS and CMS building toolkit.", + long_description=read("README.rst"), + author="Matthias Kestenholz", + author_email="mk@feinheit.ch", + url="http://github.com/feincms/feincms/", + license="BSD License", + platforms=["OS Independent"], + packages=find_packages(exclude=["tests"]), include_package_data=True, install_requires=[ - 'Django>=1.7', - 'django-mptt>=0.7.1', - 'Pillow>=2.0.0', - 'pytz>=2014.10', + "Django>=1.7", + "django-mptt>=0.7.1", + "Pillow>=2.0.0", + "pytz>=2014.10", ], classifiers=[ devstatus, - 'Environment :: Web Environment', - 'Framework :: Django', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - 'Topic :: Software Development', - 'Topic :: Software Development :: Libraries :: Application Frameworks', + "Environment :: Web Environment", + "Framework :: Django", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries :: Application Frameworks", ], zip_safe=False, ) diff --git a/tests/manage.py b/tests/manage.py index 817e86b82..e20cc5e6d 100755 --- a/tests/manage.py +++ b/tests/manage.py @@ -5,8 +5,7 @@ if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testapp.settings") - sys.path.insert( - 0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from django.core.management import execute_from_command_line diff --git a/tests/testapp/applicationcontent_urls.py b/tests/testapp/applicationcontent_urls.py index 5a1ba3cb4..92a58d13f 100644 --- a/tests/testapp/applicationcontent_urls.py +++ b/tests/testapp/applicationcontent_urls.py @@ -13,57 +13,54 @@ def module_root(request): - return HttpResponse('module_root') + return HttpResponse("module_root") def args_test(request, kwarg1, kwarg2): - return HttpResponse('%s-%s' % (kwarg1, kwarg2)) + return HttpResponse("%s-%s" % (kwarg1, kwarg2)) def full_reverse_test(request): - return render_to_string('full_reverse_test.html', {}) + return render_to_string("full_reverse_test.html", {}) def alias_reverse_test(request): - return render_to_string('alias_reverse_test.html', {}) + return render_to_string("alias_reverse_test.html", {}) def fragment(request): - return render_to_string('fragment.html', {'request': request}) + return render_to_string("fragment.html", {"request": request}) def redirect(request): - return HttpResponseRedirect(request.build_absolute_uri('../')) + return HttpResponseRedirect(request.build_absolute_uri("../")) def response(request): - return HttpResponse('Anything') + return HttpResponse("Anything") def inheritance20(request): - return 'inheritance20.html', {'from_appcontent': 42} + return "inheritance20.html", {"from_appcontent": 42} @unpack def inheritance20_unpack(request): - response = TemplateResponse( - request, - 'inheritance20.html', - {'from_appcontent': 43}) - response['Cache-Control'] = 'yabba dabba' + response = TemplateResponse(request, "inheritance20.html", {"from_appcontent": 43}) + response["Cache-Control"] = "yabba dabba" return response urlpatterns = [ - url(r'^$', module_root, name='ac_module_root'), - url(r'^args_test/([^/]+)/([^/]+)/$', args_test, name='ac_args_test'), - url(r'^kwargs_test/(?P[^/]+)/(?P[^/]+)/$', args_test), - url(r'^full_reverse_test/$', full_reverse_test), - url(r'^alias_reverse_test/$', alias_reverse_test), - url(r'^fragment/$', fragment), - url(r'^redirect/$', redirect), - url(r'^response/$', response), - url(r'^response_decorated/$', standalone(response)), - url(r'^inheritance20/$', inheritance20), - url(r'^inheritance20_unpack/$', inheritance20_unpack), + url(r"^$", module_root, name="ac_module_root"), + url(r"^args_test/([^/]+)/([^/]+)/$", args_test, name="ac_args_test"), + url(r"^kwargs_test/(?P[^/]+)/(?P[^/]+)/$", args_test), + url(r"^full_reverse_test/$", full_reverse_test), + url(r"^alias_reverse_test/$", alias_reverse_test), + url(r"^fragment/$", fragment), + url(r"^redirect/$", redirect), + url(r"^response/$", response), + url(r"^response_decorated/$", standalone(response)), + url(r"^inheritance20/$", inheritance20), + url(r"^inheritance20_unpack/$", inheritance20_unpack), ] diff --git a/tests/testapp/context_processors.py b/tests/testapp/context_processors.py index 7d0253c20..c477b6489 100644 --- a/tests/testapp/context_processors.py +++ b/tests/testapp/context_processors.py @@ -1,2 +1,2 @@ def test_context(request): - return {'THE_ANSWER': 42} + return {"THE_ANSWER": 42} diff --git a/tests/testapp/migrations/0001_initial.py b/tests/testapp/migrations/0001_initial.py index a851c06a8..dbd8263e1 100644 --- a/tests/testapp/migrations/0001_initial.py +++ b/tests/testapp/migrations/0001_initial.py @@ -9,77 +9,123 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Category', + name="Category", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=20)), - ('slug', models.SlugField()), - ('lft', models.PositiveIntegerField(db_index=True, editable=False)), - ('rght', models.PositiveIntegerField(db_index=True, editable=False)), - ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), - ('level', models.PositiveIntegerField(db_index=True, editable=False)), - ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='testapp.Category')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=20)), + ("slug", models.SlugField()), + ("lft", models.PositiveIntegerField(db_index=True, editable=False)), + ("rght", models.PositiveIntegerField(db_index=True, editable=False)), + ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)), + ("level", models.PositiveIntegerField(db_index=True, editable=False)), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="testapp.Category", + ), + ), ], options={ - 'verbose_name': 'category', - 'verbose_name_plural': 'categories', - 'ordering': ['tree_id', 'lft'], + "verbose_name": "category", + "verbose_name_plural": "categories", + "ordering": ["tree_id", "lft"], }, ), migrations.CreateModel( - name='CustomContentType', + name="CustomContentType", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('region', models.CharField(max_length=255)), - ('ordering', models.IntegerField(default=0, verbose_name='ordering')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("region", models.CharField(max_length=255)), + ("ordering", models.IntegerField(default=0, verbose_name="ordering")), ], options={ - 'verbose_name': 'custom content type', - 'verbose_name_plural': 'custom content types', - 'db_table': 'testapp_mymodel_customcontenttype', - 'ordering': ['ordering'], - 'permissions': [], - 'abstract': False, + "verbose_name": "custom content type", + "verbose_name_plural": "custom content types", + "db_table": "testapp_mymodel_customcontenttype", + "ordering": ["ordering"], + "permissions": [], + "abstract": False, }, ), migrations.CreateModel( - name='ExampleCMSBase', + name="ExampleCMSBase", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ) ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model, feincms.extensions.base.ExtensionsMixin), ), migrations.CreateModel( - name='ExampleCMSBase2', + name="ExampleCMSBase2", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ) ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model, feincms.extensions.base.ExtensionsMixin), ), migrations.CreateModel( - name='MyModel', + name="MyModel", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ) ], - options={ - 'abstract': False, - }, + options={"abstract": False}, bases=(models.Model, feincms.extensions.base.ExtensionsMixin), ), migrations.AddField( - model_name='customcontenttype', - name='parent', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customcontenttype_set', to='testapp.MyModel'), + model_name="customcontenttype", + name="parent", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="customcontenttype_set", + to="testapp.MyModel", + ), ), ] diff --git a/tests/testapp/models.py b/tests/testapp/models.py index 05328489a..c51402d95 100644 --- a/tests/testapp/models.py +++ b/tests/testapp/models.py @@ -17,69 +17,69 @@ from .content import CustomContentType -Page.register_templates({ - 'key': 'base', - 'title': 'Base Template', - 'path': 'base.html', - 'regions': ( - ('main', 'Main region'), - ('sidebar', 'Sidebar', 'inherited'), - ), -}) +Page.register_templates( + { + "key": "base", + "title": "Base Template", + "path": "base.html", + "regions": (("main", "Main region"), ("sidebar", "Sidebar", "inherited")), + } +) Page.create_content_type(RawContent) Page.create_content_type( - MediaFileContent, - TYPE_CHOICES=( - ('default', 'Default position'), - ) + MediaFileContent, TYPE_CHOICES=(("default", "Default position"),) +) +Page.create_content_type( + TemplateContent, TEMPLATES=[("templatecontent_1.html", "template 1")] ) -Page.create_content_type(TemplateContent, TEMPLATES=[ - ('templatecontent_1.html', 'template 1'), -]) Page.register_request_processor(processors.etag_request_processor) Page.register_response_processor(processors.etag_response_processor) -Page.register_response_processor( - processors.debug_sql_queries_response_processor()) +Page.register_response_processor(processors.debug_sql_queries_response_processor()) def get_admin_fields(form, *args, **kwargs): return { - 'exclusive_subpages': forms.BooleanField( - label=capfirst(_('exclusive subpages')), + "exclusive_subpages": forms.BooleanField( + label=capfirst(_("exclusive subpages")), required=False, - initial=form.instance.parameters.get('exclusive_subpages', False), + initial=form.instance.parameters.get("exclusive_subpages", False), help_text=_( - 'Exclude everything other than the application\'s content' - ' when rendering subpages.'), + "Exclude everything other than the application's content" + " when rendering subpages." + ), ), - 'custom_field': forms.CharField(), + "custom_field": forms.CharField(), } Page.create_content_type( ApplicationContent, APPLICATIONS=( - ('whatever', 'Test Urls', { - 'admin_fields': get_admin_fields, - 'urls': 'testapp.applicationcontent_urls', - }), - ) + ( + "whatever", + "Test Urls", + { + "admin_fields": get_admin_fields, + "urls": "testapp.applicationcontent_urls", + }, + ), + ), ) Page.register_extensions( - 'feincms.module.page.extensions.navigation', - 'feincms.module.page.extensions.sites', - 'feincms.extensions.translations', - 'feincms.extensions.datepublisher', - 'feincms.extensions.translations', - 'feincms.extensions.ct_tracker', - 'feincms.extensions.seo', - 'feincms.extensions.changedate', - 'feincms.extensions.seo', # duplicate - 'feincms.module.page.extensions.navigation', - 'feincms.module.page.extensions.symlinks', - 'feincms.module.page.extensions.titles', - 'feincms.module.page.extensions.navigationgroups', + "feincms.module.page.extensions.navigation", + "feincms.module.page.extensions.sites", + "feincms.extensions.translations", + "feincms.extensions.datepublisher", + "feincms.extensions.translations", + "feincms.extensions.ct_tracker", + "feincms.extensions.seo", + "feincms.extensions.changedate", + "feincms.extensions.seo", # duplicate + "feincms.module.page.extensions.navigation", + "feincms.module.page.extensions.symlinks", + "feincms.module.page.extensions.titles", + "feincms.module.page.extensions.navigationgroups", ) @@ -88,13 +88,13 @@ class Category(MPTTModel): name = models.CharField(max_length=20) slug = models.SlugField() parent = models.ForeignKey( - 'self', blank=True, null=True, related_name='children', - on_delete=models.CASCADE) + "self", blank=True, null=True, related_name="children", on_delete=models.CASCADE + ) class Meta: - ordering = ['tree_id', 'lft'] - verbose_name = 'category' - verbose_name_plural = 'categories' + ordering = ["tree_id", "lft"] + verbose_name = "category" + verbose_name_plural = "categories" def __str__(self): return self.name @@ -105,24 +105,24 @@ class ExampleCMSBase(Base): ExampleCMSBase.register_regions( - ('region', 'region title'), - ('region2', 'region2 title')) + ("region", "region title"), ("region2", "region2 title") +) class ExampleCMSBase2(Base): - pass + pass ExampleCMSBase2.register_regions( - ('region', 'region title'), - ('region2', 'region2 title')) + ("region", "region title"), ("region2", "region2 title") +) class MyModel(create_base_model()): pass -MyModel.register_regions(('main', 'Main region')) +MyModel.register_regions(("main", "Main region")) unchanged = CustomContentType diff --git a/tests/testapp/navigation_extensions.py b/tests/testapp/navigation_extensions.py index bbd691839..6400375fb 100644 --- a/tests/testapp/navigation_extensions.py +++ b/tests/testapp/navigation_extensions.py @@ -1,12 +1,11 @@ from __future__ import absolute_import, unicode_literals -from feincms.module.page.extensions.navigation import ( - NavigationExtension, PagePretender) +from feincms.module.page.extensions.navigation import NavigationExtension, PagePretender class PassthroughExtension(NavigationExtension): # See PagesTestCase.test_23_navigation_extension - name = 'passthrough extension' + name = "passthrough extension" def children(self, page, **kwargs): for p in page.children.in_navigation(): @@ -14,7 +13,7 @@ def children(self, page, **kwargs): class PretenderExtension(NavigationExtension): - name = 'pretender extension' + name = "pretender extension" def children(self, page, **kwargs): - return [PagePretender(title='blabla', url='/asdsa/')] + return [PagePretender(title="blabla", url="/asdsa/")] diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index 560875262..4df48ea5b 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -5,73 +5,68 @@ SITE_ID = 1 -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } -} +DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.admin', - 'django.contrib.contenttypes', - 'django.contrib.messages', - 'django.contrib.sessions', - 'django.contrib.sitemaps', - 'django.contrib.sites', - 'django.contrib.staticfiles', - 'feincms', - 'feincms.module.medialibrary', - 'feincms.module.page', - 'mptt', - 'testapp', + "django.contrib.auth", + "django.contrib.admin", + "django.contrib.contenttypes", + "django.contrib.messages", + "django.contrib.sessions", + "django.contrib.sitemaps", + "django.contrib.sites", + "django.contrib.staticfiles", + "feincms", + "feincms.module.medialibrary", + "feincms.module.page", + "mptt", + "testapp", ] -MEDIA_URL = '/media/' -STATIC_URL = '/static/' +MEDIA_URL = "/media/" +STATIC_URL = "/static/" BASEDIR = os.path.dirname(__file__) -MEDIA_ROOT = os.path.join(BASEDIR, 'media/') -STATIC_ROOT = os.path.join(BASEDIR, 'static/') -SECRET_KEY = 'supersikret' +MEDIA_ROOT = os.path.join(BASEDIR, "media/") +STATIC_ROOT = os.path.join(BASEDIR, "static/") +SECRET_KEY = "supersikret" USE_TZ = True -ROOT_URLCONF = 'testapp.urls' -LANGUAGES = (('en', 'English'), ('de', 'German')) +ROOT_URLCONF = "testapp.urls" +LANGUAGES = (("en", "English"), ("de", "German")) TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.static', + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.static", # request context processor is needed - 'django.core.context_processors.request', - 'testapp.context_processors.test_context', + "django.core.context_processors.request", + "testapp.context_processors.test_context", ) TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'testapp.context_processors.test_context', - ], + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "testapp.context_processors.test_context", + ] }, - }, + } ] MIDDLEWARE = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.locale.LocaleMiddleware' + "django.middleware.common.CommonMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.locale.LocaleMiddleware", ) if django.VERSION < (1, 11): @@ -79,10 +74,12 @@ if (2,) <= django.VERSION < (2, 1): from django.utils import deprecation + # Anything to make mptt.templatetags.mptt_admin importable deprecation.RemovedInDjango20Warning = deprecation.RemovedInDjango21Warning elif django.VERSION >= (2,): from django.utils import deprecation + # Anything to make mptt.templatetags.mptt_admin importable deprecation.RemovedInDjango20Warning = deprecation.RemovedInDjango30Warning diff --git a/tests/testapp/tests/test_cms.py b/tests/testapp/tests/test_cms.py index 557b65125..1e818ed72 100644 --- a/tests/testapp/tests/test_cms.py +++ b/tests/testapp/tests/test_cms.py @@ -21,13 +21,13 @@ from django.utils.unittest import skipIf skipUnlessLegacy = skipIf( - django.VERSION >= (1, 8), - "Legacy tests only necessary in Django < 1.8") + django.VERSION >= (1, 8), "Legacy tests only necessary in Django < 1.8" +) # ------------------------------------------------------------------------ class SubRawContent(RawContent): - title = models.CharField('title', max_length=100, blank=True) + title = models.CharField("title", max_length=100, blank=True) class Meta: abstract = True @@ -37,7 +37,7 @@ class CMSBaseTest(TestCase): def test_01_simple_content_type_creation(self): self.assertEqual(ExampleCMSBase.content_type_for(RawContent), None) - ExampleCMSBase.create_content_type(RawContent, regions=('main2',)) + ExampleCMSBase.create_content_type(RawContent, regions=("main2",)) ExampleCMSBase.create_content_type(RichTextContent) # content_type_for should return None if it does not have a subclass @@ -45,8 +45,9 @@ def test_01_simple_content_type_creation(self): self.assertEqual(ExampleCMSBase.content_type_for(Empty), None) self.assertTrue( - 'rawcontent' not in dict( - ExampleCMSBase.template.regions[0].content_types).keys()) + "rawcontent" + not in dict(ExampleCMSBase.template.regions[0].content_types).keys() + ) def test_04_mediafilecontent_creation(self): # the medialibrary needs to be enabled, otherwise this test fails @@ -54,7 +55,8 @@ def test_04_mediafilecontent_creation(self): # no TYPE_CHOICES, should raise self.assertRaises( ImproperlyConfigured, - lambda: ExampleCMSBase.create_content_type(MediaFileContent)) + lambda: ExampleCMSBase.create_content_type(MediaFileContent), + ) def test_05_non_abstract_content_type(self): # Should not be able to create a content type from a non-abstract base @@ -64,7 +66,8 @@ class TestContentType(models.Model): self.assertRaises( ImproperlyConfigured, - lambda: ExampleCMSBase.create_content_type(TestContentType)) + lambda: ExampleCMSBase.create_content_type(TestContentType), + ) def test_07_default_render_method(self): class SomethingElse(models.Model): @@ -72,29 +75,23 @@ class Meta: abstract = True def render_region(self): - return 'hello' + return "hello" type = ExampleCMSBase.create_content_type(SomethingElse) obj = type() self.assertRaises(NotImplementedError, lambda: obj.render()) - obj.region = 'region' - self.assertEqual(obj.render(), 'hello') + obj.region = "region" + self.assertEqual(obj.render(), "hello") def test_08_creating_two_content_types_in_same_application(self): ExampleCMSBase.create_content_type(RawContent) ct = ExampleCMSBase.content_type_for(RawContent) - self.assertEqual( - ct._meta.db_table, - 'testapp_examplecmsbase_rawcontent') + self.assertEqual(ct._meta.db_table, "testapp_examplecmsbase_rawcontent") - ExampleCMSBase2.create_content_type( - RawContent, - class_name='RawContent2') + ExampleCMSBase2.create_content_type(RawContent, class_name="RawContent2") ct2 = ExampleCMSBase2.content_type_for(RawContent) - self.assertEqual( - ct2._meta.db_table, - 'testapp_examplecmsbase2_rawcontent2') + self.assertEqual(ct2._meta.db_table, "testapp_examplecmsbase2_rawcontent2") @skipUnlessLegacy def test_09_related_objects_cache(self): @@ -111,33 +108,31 @@ def test_09_related_objects_cache(self): It also fails on Django 1.7 since the introduction of django.apps """ + class Attachment(models.Model): - base = models.ForeignKey( - ExampleCMSBase, - related_name='test_related_name') + base = models.ForeignKey(ExampleCMSBase, related_name="test_related_name") # See issue #323 on Github. ExampleCMSBase._meta._fill_related_objects_cache() related_models = map( - lambda x: x.model, ExampleCMSBase._meta.get_all_related_objects()) + lambda x: x.model, ExampleCMSBase._meta.get_all_related_objects() + ) self.assertTrue(Attachment in related_models) - self.assertTrue(hasattr(ExampleCMSBase, 'test_related_name')) + self.assertTrue(hasattr(ExampleCMSBase, "test_related_name")) # self.assertFalse(hasattr(Attachment, 'anycontents')) class AnyContent(models.Model): - attachment = models.ForeignKey( - Attachment, - related_name='anycontents') + attachment = models.ForeignKey(Attachment, related_name="anycontents") class Meta: abstract = True ExampleCMSBase.create_content_type(AnyContent) - self.assertTrue(hasattr(ExampleCMSBase, 'test_related_name')) - self.assertTrue(hasattr(Attachment, 'anycontents')) + self.assertTrue(hasattr(ExampleCMSBase, "test_related_name")) + self.assertTrue(hasattr(Attachment, "anycontents")) def test_10_content_type_subclasses(self): """ diff --git a/tests/testapp/tests/test_extensions.py b/tests/testapp/tests/test_extensions.py index 10c7dc987..4994ceabb 100644 --- a/tests/testapp/tests/test_extensions.py +++ b/tests/testapp/tests/test_extensions.py @@ -15,20 +15,22 @@ class TranslationTestCase(TestCase): def setUp(self): - Page.register_templates({ - 'key': 'base', - 'title': 'Standard template', - 'path': 'feincms_base.html', - 'regions': ( - ('main', 'Main content area'), - ('sidebar', 'Sidebar', 'inherited'), - ), - }) + Page.register_templates( + { + "key": "base", + "title": "Standard template", + "path": "feincms_base.html", + "regions": ( + ("main", "Main content area"), + ("sidebar", "Sidebar", "inherited"), + ), + } + ) self.site_1 = Site.objects.all()[0] # create a bunch of pages - en = self.create_default_page_set(language='en') - de = self.create_default_page_set(language='de', title='Testseite') + en = self.create_default_page_set(language="en") + de = self.create_default_page_set(language="de", title="Testseite") de.translation_of = en de.save() de.parent.translation_of = en.parent @@ -36,36 +38,34 @@ def setUp(self): self.page_de = de.parent self.page_en = en.parent - if hasattr(translation, 'LANGUAGE_SESSION_KEY'): + if hasattr(translation, "LANGUAGE_SESSION_KEY"): self.language_session_key = translation.LANGUAGE_SESSION_KEY else: # Django 1.6 self.language_session_key = django_settings.LANGUAGE_COOKIE_NAME - def create_page(self, title='Test page', parent=None, **kwargs): + def create_page(self, title="Test page", parent=None, **kwargs): defaults = { - 'template_key': 'base', - 'site': self.site_1, - 'in_navigation': False, - 'active': False, + "template_key": "base", + "site": self.site_1, + "in_navigation": False, + "active": False, } defaults.update(kwargs) return Page.objects.create( title=title, - slug=kwargs.get('slug', slugify(title)), + slug=kwargs.get("slug", slugify(title)), parent=parent, - **defaults) + **defaults + ) def create_default_page_set(self, **kwargs): - return self.create_page( - 'Test child page', - parent=self.create_page(**kwargs), - ) + return self.create_page("Test child page", parent=self.create_page(**kwargs)) def testPage(self): page = Page() - self.assertTrue(hasattr(page, 'language')) - self.assertTrue(hasattr(page, 'translation_of')) + self.assertTrue(hasattr(page, "language")) + self.assertTrue(hasattr(page, "translation_of")) self.assertEqual(self.page_de.translation_of, self.page_en) self.assertEqual(self.page_de.original_translation, self.page_en) @@ -75,32 +75,32 @@ def testPage(self): def test_user_has_language_set_with_session(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) - setattr(request, 'session', dict()) - request.session[self.language_session_key] = 'en' + setattr(request, "session", dict()) + request.session[self.language_session_key] = "en" self.assertEqual(user_has_language_set(request), True) def test_user_has_language_set_with_cookie(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) - request.COOKIES[django_settings.LANGUAGE_COOKIE_NAME] = 'en' + request.COOKIES[django_settings.LANGUAGE_COOKIE_NAME] = "en" self.assertEqual(user_has_language_set(request), True) def test_translation_set_language_to_session(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) - setattr(request, 'session', dict()) - translation_set_language(request, 'en') + setattr(request, "session", dict()) + translation_set_language(request, "en") - self.assertEqual(request.LANGUAGE_CODE, 'en') - self.assertEqual(request.session[self.language_session_key], 'en') + self.assertEqual(request.LANGUAGE_CODE, "en") + self.assertEqual(request.session[self.language_session_key], "en") def test_translation_set_language_to_cookie(self): factory = RequestFactory() request = factory.get(self.page_en.get_navigation_url()) - response = translation_set_language(request, 'en') + response = translation_set_language(request, "en") - self.assertEqual(request.LANGUAGE_CODE, 'en') + self.assertEqual(request.LANGUAGE_CODE, "en") c_key = django_settings.LANGUAGE_COOKIE_NAME - self.assertEqual(response.cookies[c_key].value, 'en') + self.assertEqual(response.cookies[c_key].value, "en") diff --git a/tests/testapp/tests/test_page.py b/tests/testapp/tests/test_page.py index a9c48e0e8..789222679 100644 --- a/tests/testapp/tests/test_page.py +++ b/tests/testapp/tests/test_page.py @@ -20,6 +20,7 @@ from django.test import TestCase from django.utils import timezone from django.utils.encoding import force_text + try: from django.urls import reverse except ImportError: @@ -45,202 +46,189 @@ # ------------------------------------------------------------------------ class PagesTestCase(TestCase): def setUp(self): - u = User( - username='test', - is_active=True, - is_staff=True, - is_superuser=True) - u.set_password('test') + u = User(username="test", is_active=True, is_staff=True, is_superuser=True) + u.set_password("test") u.save() self.site_1 = Site.objects.all()[0] - Page.register_templates({ - 'key': 'base', - 'title': 'Standard template', - 'path': 'feincms_base.html', - 'regions': ( - ('main', 'Main content area'), - ('sidebar', 'Sidebar', 'inherited'), - ), - }, { - 'key': 'theother', - 'title': 'This actually exists', - 'path': 'base.html', - 'regions': ( - ('main', 'Main content area'), - ('sidebar', 'Sidebar', 'inherited'), - ), - }) + Page.register_templates( + { + "key": "base", + "title": "Standard template", + "path": "feincms_base.html", + "regions": ( + ("main", "Main content area"), + ("sidebar", "Sidebar", "inherited"), + ), + }, + { + "key": "theother", + "title": "This actually exists", + "path": "base.html", + "regions": ( + ("main", "Main content area"), + ("sidebar", "Sidebar", "inherited"), + ), + }, + ) def login(self): - self.assertTrue(self.client.login(username='test', password='test')) + self.assertTrue(self.client.login(username="test", password="test")) - def create_page_through_admin(self, title='Test page', parent='', - **kwargs): + def create_page_through_admin(self, title="Test page", parent="", **kwargs): dic = { - 'title': title, - 'slug': kwargs.get('slug', slugify(title)), - 'parent': parent, - 'template_key': 'base', - 'publication_date_0': '2009-01-01', - 'publication_date_1': '00:00:00', - 'initial-publication_date_0': '2009-01-01', - 'initial-publication_date_1': '00:00:00', - 'language': 'en', - 'navigation_group': 'default', - 'site': self.site_1.id, - - 'rawcontent_set-TOTAL_FORMS': 0, - 'rawcontent_set-INITIAL_FORMS': 0, - 'rawcontent_set-MAX_NUM_FORMS': 10, - - 'mediafilecontent_set-TOTAL_FORMS': 0, - 'mediafilecontent_set-INITIAL_FORMS': 0, - 'mediafilecontent_set-MAX_NUM_FORMS': 10, - - 'imagecontent_set-TOTAL_FORMS': 0, - 'imagecontent_set-INITIAL_FORMS': 0, - 'imagecontent_set-MAX_NUM_FORMS': 10, - - 'contactformcontent_set-TOTAL_FORMS': 0, - 'contactformcontent_set-INITIAL_FORMS': 0, - 'contactformcontent_set-MAX_NUM_FORMS': 10, - - 'filecontent_set-TOTAL_FORMS': 0, - 'filecontent_set-INITIAL_FORMS': 0, - 'filecontent_set-MAX_NUM_FORMS': 10, - - 'templatecontent_set-TOTAL_FORMS': 0, - 'templatecontent_set-INITIAL_FORMS': 0, - 'templatecontent_set-MAX_NUM_FORMS': 10, - - 'applicationcontent_set-TOTAL_FORMS': 0, - 'applicationcontent_set-INITIAL_FORMS': 0, - 'applicationcontent_set-MAX_NUM_FORMS': 10, + "title": title, + "slug": kwargs.get("slug", slugify(title)), + "parent": parent, + "template_key": "base", + "publication_date_0": "2009-01-01", + "publication_date_1": "00:00:00", + "initial-publication_date_0": "2009-01-01", + "initial-publication_date_1": "00:00:00", + "language": "en", + "navigation_group": "default", + "site": self.site_1.id, + "rawcontent_set-TOTAL_FORMS": 0, + "rawcontent_set-INITIAL_FORMS": 0, + "rawcontent_set-MAX_NUM_FORMS": 10, + "mediafilecontent_set-TOTAL_FORMS": 0, + "mediafilecontent_set-INITIAL_FORMS": 0, + "mediafilecontent_set-MAX_NUM_FORMS": 10, + "imagecontent_set-TOTAL_FORMS": 0, + "imagecontent_set-INITIAL_FORMS": 0, + "imagecontent_set-MAX_NUM_FORMS": 10, + "contactformcontent_set-TOTAL_FORMS": 0, + "contactformcontent_set-INITIAL_FORMS": 0, + "contactformcontent_set-MAX_NUM_FORMS": 10, + "filecontent_set-TOTAL_FORMS": 0, + "filecontent_set-INITIAL_FORMS": 0, + "filecontent_set-MAX_NUM_FORMS": 10, + "templatecontent_set-TOTAL_FORMS": 0, + "templatecontent_set-INITIAL_FORMS": 0, + "templatecontent_set-MAX_NUM_FORMS": 10, + "applicationcontent_set-TOTAL_FORMS": 0, + "applicationcontent_set-INITIAL_FORMS": 0, + "applicationcontent_set-MAX_NUM_FORMS": 10, } dic.update(kwargs) - return self.client.post('/admin/page/page/add/', dic) + return self.client.post("/admin/page/page/add/", dic) def create_default_page_set_through_admin(self): self.login() self.create_page_through_admin() - return self.create_page_through_admin('Test child page', 1) + return self.create_page_through_admin("Test child page", 1) - def create_page(self, title='Test page', parent=None, **kwargs): + def create_page(self, title="Test page", parent=None, **kwargs): defaults = { - 'template_key': 'base', - 'site': self.site_1, - 'in_navigation': False, - 'active': False, - 'navigation_group': 'default', + "template_key": "base", + "site": self.site_1, + "in_navigation": False, + "active": False, + "navigation_group": "default", } defaults.update(kwargs) return Page.objects.create( title=title, - slug=kwargs.get('slug', slugify(title)), + slug=kwargs.get("slug", slugify(title)), parent=parent, - **defaults) + **defaults + ) def create_default_page_set(self): - self.create_page( - 'Test child page', - parent=self.create_page(), - ) + self.create_page("Test child page", parent=self.create_page()) def is_published(self, url, should_be=True): try: self.client.get(url) except TemplateDoesNotExist as e: if should_be: - if e.args != ('feincms_base.html',): + if e.args != ("feincms_base.html",): raise else: - if e.args != ('404.html',): + if e.args != ("404.html",): raise def test_01_tree_editor(self): self.login() - self.assertEqual( - self.client.get('/admin/page/page/').status_code, 200) + self.assertEqual(self.client.get("/admin/page/page/").status_code, 200) self.assertRedirects( - self.client.get('/admin/page/page/?anything=anything'), - '/admin/page/page/?e=1') + self.client.get("/admin/page/page/?anything=anything"), + "/admin/page/page/?e=1", + ) def test_02_add_page(self): self.login() self.assertRedirects( - self.create_page_through_admin( - title='Test page ' * 10, - slug='test-page'), - '/admin/page/page/') + self.create_page_through_admin(title="Test page " * 10, slug="test-page"), + "/admin/page/page/", + ) self.assertEqual(Page.objects.count(), 1) - self.assertContains(self.client.get('/admin/page/page/'), '…') + self.assertContains(self.client.get("/admin/page/page/"), "…") def test_03_item_editor(self): self.login() self.assertRedirects( self.create_page_through_admin(_continue=1), - reverse('admin:page_page_change', args=(1,))) + reverse("admin:page_page_change", args=(1,)), + ) self.assertEqual( - self.client.get( - reverse('admin:page_page_change', args=(1,)) - ).status_code, 200) + self.client.get(reverse("admin:page_page_change", args=(1,))).status_code, + 200, + ) self.is_published( - reverse('admin:page_page_change', args=(42,)), - should_be=False) + reverse("admin:page_page_change", args=(42,)), should_be=False + ) def test_03_add_another(self): self.login() self.assertRedirects( - self.create_page_through_admin(_addanother=1), - '/admin/page/page/add/') + self.create_page_through_admin(_addanother=1), "/admin/page/page/add/" + ) def test_04_add_child(self): response = self.create_default_page_set_through_admin() - self.assertRedirects(response, '/admin/page/page/') + self.assertRedirects(response, "/admin/page/page/") self.assertEqual(Page.objects.count(), 2) page = Page.objects.get(pk=2) - self.assertEqual( - page.get_absolute_url(), '/test-page/test-child-page/') + self.assertEqual(page.get_absolute_url(), "/test-page/test-child-page/") page.active = True page.in_navigation = True page.save() # page2 inherited the inactive flag from the toplevel page - self.assertContains(self.client.get('/admin/page/page/'), 'inherited') + self.assertContains(self.client.get("/admin/page/page/"), "inherited") page1 = Page.objects.get(pk=1) page1.active = True page1.save() - content = self.client.get('/admin/page/page/').content.decode('utf-8') + content = self.client.get("/admin/page/page/").content.decode("utf-8") self.assertEqual(len(content.split('checked="checked"')), 4) def test_05_override_url(self): self.create_default_page_set() page = Page.objects.get(pk=1) - page.override_url = '/something/' + page.override_url = "/something/" page.save() page2 = Page.objects.get(pk=2) - self.assertEqual( - page2.get_absolute_url(), '/something/test-child-page/') + self.assertEqual(page2.get_absolute_url(), "/something/test-child-page/") - page.override_url = '/' + page.override_url = "/" page.save() page2 = Page.objects.get(pk=2) - self.assertEqual(page2.get_absolute_url(), '/test-child-page/') + self.assertEqual(page2.get_absolute_url(), "/test-child-page/") - self.is_published('/', False) + self.is_published("/", False) page.active = True - page.template_key = 'theother' + page.template_key = "theother" page.save() - self.is_published('/', True) + self.is_published("/", True) def test_06_tree_editor_save(self): self.create_default_page_set() @@ -248,29 +236,32 @@ def test_06_tree_editor_save(self): page1 = Page.objects.get(pk=1) page2 = Page.objects.get(pk=2) - page3 = Page.objects.create(title='page3', slug='page3', parent=page2) - page4 = Page.objects.create(title='page4', slug='page4', parent=page1) - page5 = Page.objects.create(title='page5', slug='page5', parent=None) + page3 = Page.objects.create(title="page3", slug="page3", parent=page2) + page4 = Page.objects.create(title="page4", slug="page4", parent=page1) + page5 = Page.objects.create(title="page5", slug="page5", parent=None) - self.assertEqual( - page3.get_absolute_url(), '/test-page/test-child-page/page3/') - self.assertEqual(page4.get_absolute_url(), '/test-page/page4/') - self.assertEqual(page5.get_absolute_url(), '/page5/') + self.assertEqual(page3.get_absolute_url(), "/test-page/test-child-page/page3/") + self.assertEqual(page4.get_absolute_url(), "/test-page/page4/") + self.assertEqual(page5.get_absolute_url(), "/page5/") self.login() - self.client.post('/admin/page/page/', { - '__cmd': 'move_node', - 'position': 'last-child', - 'cut_item': '1', - 'pasted_on': '5', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest') - - self.assertEqual(Page.objects.get(pk=1).get_absolute_url(), - '/page5/test-page/') - self.assertEqual(Page.objects.get(pk=5).get_absolute_url(), - '/page5/') - self.assertEqual(Page.objects.get(pk=3).get_absolute_url(), - '/page5/test-page/test-child-page/page3/') + self.client.post( + "/admin/page/page/", + { + "__cmd": "move_node", + "position": "last-child", + "cut_item": "1", + "pasted_on": "5", + }, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ) + + self.assertEqual(Page.objects.get(pk=1).get_absolute_url(), "/page5/test-page/") + self.assertEqual(Page.objects.get(pk=5).get_absolute_url(), "/page5/") + self.assertEqual( + Page.objects.get(pk=3).get_absolute_url(), + "/page5/test-page/test-child-page/page3/", + ) def test_07_tree_editor_toggle_boolean(self): self.create_default_page_set() @@ -279,38 +270,46 @@ def test_07_tree_editor_toggle_boolean(self): self.login() self.assertContains( - self.client.post('/admin/page/page/', { - '__cmd': 'toggle_boolean', - 'item_id': 1, - 'attr': 'in_navigation', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - r'checked=\"checked\"') + self.client.post( + "/admin/page/page/", + {"__cmd": "toggle_boolean", "item_id": 1, "attr": "in_navigation"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + r"checked=\"checked\"", + ) self.assertEqual(Page.objects.get(pk=1).in_navigation, True) self.assertNotContains( - self.client.post('/admin/page/page/', { - '__cmd': 'toggle_boolean', - 'item_id': 1, - 'attr': 'in_navigation', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - 'checked="checked"') + self.client.post( + "/admin/page/page/", + {"__cmd": "toggle_boolean", "item_id": 1, "attr": "in_navigation"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + 'checked="checked"', + ) self.assertEqual(Page.objects.get(pk=1).in_navigation, False) - self.assertTrue(isinstance( - self.client.post('/admin/page/page/', { - '__cmd': 'toggle_boolean', - 'item_id': 1, - 'attr': 'notexists', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - HttpResponseBadRequest)) + self.assertTrue( + isinstance( + self.client.post( + "/admin/page/page/", + {"__cmd": "toggle_boolean", "item_id": 1, "attr": "notexists"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + HttpResponseBadRequest, + ) + ) def test_07_tree_editor_invalid_ajax(self): self.login() self.assertContains( - self.client.post('/admin/page/page/', { - '__cmd': 'notexists', - }, HTTP_X_REQUESTED_WITH='XMLHttpRequest'), - 'Oops. AJAX request not understood.', - status_code=400) + self.client.post( + "/admin/page/page/", + {"__cmd": "notexists"}, + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ), + "Oops. AJAX request not understood.", + status_code=400, + ) def test_08_publishing(self): self.create_default_page_set() @@ -350,47 +349,41 @@ def test_08_publishing(self): def create_page_through_admincontent(self, page, **kwargs): data = { - 'title': page.title, - 'slug': page.slug, + "title": page.title, + "slug": page.slug, # 'parent': page.parent_id, # this field is excluded from the form - 'template_key': page.template_key, - 'publication_date_0': '2009-01-01', - 'publication_date_1': '00:00:00', - 'initial-publication_date_0': '2009-01-01', - 'initial-publication_date_1': '00:00:00', - 'language': 'en', - 'navigation_group': 'default', - 'site': self.site_1.id, - - 'rawcontent_set-TOTAL_FORMS': 1, - 'rawcontent_set-INITIAL_FORMS': 0, - 'rawcontent_set-MAX_NUM_FORMS': 10, - - 'rawcontent_set-0-parent': 1, - 'rawcontent_set-0-region': 'main', - 'rawcontent_set-0-ordering': 0, - 'rawcontent_set-0-text': 'This is some example content', - - 'mediafilecontent_set-TOTAL_FORMS': 1, - 'mediafilecontent_set-INITIAL_FORMS': 0, - 'mediafilecontent_set-MAX_NUM_FORMS': 10, - - 'mediafilecontent_set-0-parent': 1, - 'mediafilecontent_set-0-type': 'default', - - 'templatecontent_set-TOTAL_FORMS': 1, - 'templatecontent_set-INITIAL_FORMS': 0, - 'templatecontent_set-MAX_NUM_FORMS': 10, - - 'applicationcontent_set-TOTAL_FORMS': 1, - 'applicationcontent_set-INITIAL_FORMS': 0, - 'applicationcontent_set-MAX_NUM_FORMS': 10, + "template_key": page.template_key, + "publication_date_0": "2009-01-01", + "publication_date_1": "00:00:00", + "initial-publication_date_0": "2009-01-01", + "initial-publication_date_1": "00:00:00", + "language": "en", + "navigation_group": "default", + "site": self.site_1.id, + "rawcontent_set-TOTAL_FORMS": 1, + "rawcontent_set-INITIAL_FORMS": 0, + "rawcontent_set-MAX_NUM_FORMS": 10, + "rawcontent_set-0-parent": 1, + "rawcontent_set-0-region": "main", + "rawcontent_set-0-ordering": 0, + "rawcontent_set-0-text": "This is some example content", + "mediafilecontent_set-TOTAL_FORMS": 1, + "mediafilecontent_set-INITIAL_FORMS": 0, + "mediafilecontent_set-MAX_NUM_FORMS": 10, + "mediafilecontent_set-0-parent": 1, + "mediafilecontent_set-0-type": "default", + "templatecontent_set-TOTAL_FORMS": 1, + "templatecontent_set-INITIAL_FORMS": 0, + "templatecontent_set-MAX_NUM_FORMS": 10, + "applicationcontent_set-TOTAL_FORMS": 1, + "applicationcontent_set-INITIAL_FORMS": 0, + "applicationcontent_set-MAX_NUM_FORMS": 10, } data.update(kwargs) return self.client.post( - reverse('admin:page_page_change', args=(page.pk,)), - data) + reverse("admin:page_page_change", args=(page.pk,)), data + ) def test_09_pagecontent(self): self.create_default_page_set() @@ -398,8 +391,8 @@ def test_09_pagecontent(self): page = Page.objects.get(pk=1) self.login() response = self.create_page_through_admincontent(page) - self.assertRedirects(response, '/admin/page/page/') - self.assertEqual(page.content.main[0].__class__.__name__, 'RawContent') + self.assertRedirects(response, "/admin/page/page/") + self.assertEqual(page.content.main[0].__class__.__name__, "RawContent") page2 = Page.objects.get(pk=2) page2.symlinked_page = page @@ -408,12 +401,12 @@ def test_09_pagecontent(self): # other content methods self.assertEqual(len(page2.content.all_of_type(RawContent)), 1) - self.assertEqual( - page2.content.main[0].__class__.__name__, 'RawContent') + self.assertEqual(page2.content.main[0].__class__.__name__, "RawContent") self.assertEqual( force_text(page2.content.main[0]), - 'RawContent, region=main,' - ' ordering=0>') + "RawContent, region=main," + " ordering=0>", + ) self.assertEqual(len(page2.content.main), 1) self.assertEqual(len(page2.content.sidebar), 0) @@ -430,73 +423,72 @@ def test_10_mediafile_and_imagecontent(self): page = Page.objects.get(pk=1) self.create_page_through_admincontent(page) - category = Category.objects.create(title='Category', parent=None) - category2 = Category.objects.create(title='Something', parent=category) + category = Category.objects.create(title="Category", parent=None) + category2 = Category.objects.create(title="Something", parent=category) - self.assertEqual(force_text(category2), 'Category - Something') - self.assertEqual(force_text(category), 'Category') + self.assertEqual(force_text(category2), "Category - Something") + self.assertEqual(force_text(category), "Category") - mediafile = MediaFile.objects.create(file='somefile.jpg') + mediafile = MediaFile.objects.create(file="somefile.jpg") if django.VERSION < (2, 0): mediafile.categories = [category] else: mediafile.categories.set([category]) page.mediafilecontent_set.create( - mediafile=mediafile, - region='main', - type='default', - ordering=1) + mediafile=mediafile, region="main", type="default", ordering=1 + ) - self.assertEqual(force_text(mediafile), 'somefile.jpg') + self.assertEqual(force_text(mediafile), "somefile.jpg") mediafile.translations.create( - caption='something', language_code='%s-ha' % short_language_code()) + caption="something", language_code="%s-ha" % short_language_code() + ) mediafile.purge_translation_cache() - self.assertTrue('something' in force_text(mediafile)) + self.assertTrue("something" in force_text(mediafile)) mf = page.content.main[1].mediafile - self.assertEqual(mf.translation.caption, 'something') - self.assertEqual( - mf.translation.short_language_code(), short_language_code()) - self.assertNotEqual(mf.get_absolute_url(), '') - self.assertEqual(force_text(mf), 'something') - self.assertTrue(mf.type == 'image') + self.assertEqual(mf.translation.caption, "something") + self.assertEqual(mf.translation.short_language_code(), short_language_code()) + self.assertNotEqual(mf.get_absolute_url(), "") + self.assertEqual(force_text(mf), "something") + self.assertTrue(mf.type == "image") - self.assertEqual(MediaFile.objects.only_language('de').count(), 0) - self.assertEqual(MediaFile.objects.only_language('en').count(), 0) + self.assertEqual(MediaFile.objects.only_language("de").count(), 0) + self.assertEqual(MediaFile.objects.only_language("en").count(), 0) self.assertEqual( MediaFile.objects.only_language( - lambda: '%s-ha' % short_language_code() + lambda: "%s-ha" % short_language_code() ).count(), 1, ) - self.assertTrue( - '%s-ha' % short_language_code() in mf.available_translations) + self.assertTrue("%s-ha" % short_language_code() in mf.available_translations) # this should not raise - self.client.get(reverse('admin:page_page_change', args=(1,))) + self.client.get(reverse("admin:page_page_change", args=(1,))) page.mediafilecontent_set.update(mediafile=3) # this should not raise - self.client.get('/admin/page/page/1/') + self.client.get("/admin/page/page/1/") - field = MediaFile._meta.get_field('file') + field = MediaFile._meta.get_field("file") old = (field.upload_to, field.storage, field.generate_filename) from django.core.files.storage import FileSystemStorage + MediaFile.reconfigure( - upload_to=lambda: 'anywhere', - storage=FileSystemStorage(location='/wha/', base_url='/whe/')) + upload_to=lambda: "anywhere", + storage=FileSystemStorage(location="/wha/", base_url="/whe/"), + ) mediafile = MediaFile.objects.get(pk=1) - self.assertEqual(mediafile.file.url, '/whe/somefile.jpg') + self.assertEqual(mediafile.file.url, "/whe/somefile.jpg") # restore settings (field.upload_to, field.storage, field.generate_filename) = old mediafile = MediaFile.objects.get(pk=1) - self.assertEqual(mediafile.file.url, '/media/somefile.jpg') + self.assertEqual(mediafile.file.url, "/media/somefile.jpg") def test_11_translations(self): self.create_default_page_set() @@ -511,7 +503,7 @@ def test_11_translations(self): page1.save() page2.active = True - page2.language = 'de' + page2.language = "de" page2.save() self.assertEqual(len(page2.available_translations()), 0) @@ -533,62 +525,50 @@ def test_12_titles(self): self.assertEqual(page.page_title, page.title) self.assertEqual(page.content_title, page.title) - page._content_title = 'Something\nawful' - page._page_title = 'Hello world' + page._content_title = "Something\nawful" + page._page_title = "Hello world" page.save() - self.assertEqual(page.page_title, 'Hello world') - self.assertEqual(page.content_title, 'Something') - self.assertEqual(page.content_subtitle, 'awful') + self.assertEqual(page.page_title, "Hello world") + self.assertEqual(page.content_title, "Something") + self.assertEqual(page.content_subtitle, "awful") - page._content_title = 'Only one line' - self.assertEqual(page.content_title, 'Only one line') - self.assertEqual(page.content_subtitle, '') + page._content_title = "Only one line" + self.assertEqual(page.content_title, "Only one line") + self.assertEqual(page.content_subtitle, "") - page._content_title = '' + page._content_title = "" self.assertEqual(page.content_title, page.title) - self.assertEqual(page.content_subtitle, '') + self.assertEqual(page.content_subtitle, "") def test_13_inheritance_and_ct_tracker(self): self.create_default_page_set() page = Page.objects.get(pk=1) - page.rawcontent_set.create( - region='sidebar', - ordering=0, - text='Something') - page.rawcontent_set.create( - region='main', - ordering=0, - text='Anything') + page.rawcontent_set.create(region="sidebar", ordering=0, text="Something") + page.rawcontent_set.create(region="main", ordering=0, text="Anything") page2 = Page.objects.get(pk=2) - page2.rawcontent_set.create( - region='main', - ordering=0, - text='Something else') - page2.rawcontent_set.create( - region='main', - ordering=1, - text='Whatever') + page2.rawcontent_set.create(region="main", ordering=0, text="Something else") + page2.rawcontent_set.create(region="main", ordering=1, text="Whatever") # Set default, non-caching content proxy page2.content_proxy_class = ContentProxy - if hasattr(self, 'assertNumQueries'): + if hasattr(self, "assertNumQueries"): # 4 queries: Two to get the content types of page and page2, one to # fetch all ancestor PKs of page2 and one to materialize the # RawContent instances belonging to page's sidebar and page2's # main. self.assertNumQueries( - 4, lambda: [page2.content.main, page2.content.sidebar]) - self.assertNumQueries( - 0, lambda: page2.content.sidebar[0].render()) + 4, lambda: [page2.content.main, page2.content.sidebar] + ) + self.assertNumQueries(0, lambda: page2.content.sidebar[0].render()) self.assertEqual( - ''.join(c.render() for c in page2.content.main), - 'Something elseWhatever') - self.assertEqual(page2.content.sidebar[0].render(), 'Something') + "".join(c.render() for c in page2.content.main), "Something elseWhatever" + ) + self.assertEqual(page2.content.sidebar[0].render(), "Something") page2 = Page.objects.get(pk=2) self.assertEqual(page2._ct_inventory, {}) @@ -597,7 +577,7 @@ def test_13_inheritance_and_ct_tracker(self): for ct in Page._feincms_content_types: ContentType.objects.get_for_model(ct) - if hasattr(self, 'assertNumQueries'): + if hasattr(self, "assertNumQueries"): # 5 queries: Two to get the content types of page and page2, one to # fetch all ancestor PKs of page2 and one to materialize the # RawContent instances belonging to page's sidebar and page2's main @@ -606,18 +586,19 @@ def test_13_inheritance_and_ct_tracker(self): # - one update to clobber the _ct_inventory attribute of all # descendants of page2 self.assertNumQueries( - 5, lambda: [page2.content.main, page2.content.sidebar]) - self.assertNumQueries( - 0, lambda: page2.content.sidebar[0].render()) + 5, lambda: [page2.content.main, page2.content.sidebar] + ) + self.assertNumQueries(0, lambda: page2.content.sidebar[0].render()) - self.assertEqual(page2.content.sidebar[0].render(), 'Something') + self.assertEqual(page2.content.sidebar[0].render(), "Something") # Reload, again, to test ct_tracker extension page2 = Page.objects.get(pk=2) - if hasattr(self, 'assertNumQueries'): + if hasattr(self, "assertNumQueries"): self.assertNumQueries( - 1, lambda: [page2.content.main, page2.content.sidebar]) + 1, lambda: [page2.content.main, page2.content.sidebar] + ) self.assertNotEqual(page2._ct_inventory, {}) @@ -625,13 +606,13 @@ def test_14_richtext(self): # only create the content type to test the item editor # customization hooks tmp = Page._feincms_content_types[:] - type = Page.create_content_type( - RichTextContent, regions=('notexists',)) + type = Page.create_content_type(RichTextContent, regions=("notexists",)) Page._feincms_content_types = tmp from django.utils.safestring import SafeData + obj = type() - obj.text = 'Something' + obj.text = "Something" self.assertTrue(isinstance(obj.render(), SafeData)) def test_17_page_template_tags(self): @@ -640,7 +621,7 @@ def test_17_page_template_tags(self): page1 = Page.objects.get(pk=1) page2 = Page.objects.get(pk=2) - page2.language = 'de' + page2.language = "de" page2.translation_of = page1 page2.active = True page2.in_navigation = True @@ -648,9 +629,9 @@ def test_17_page_template_tags(self): page3 = Page.objects.create( parent=page2, - title='page3', - slug='page3', - language='en', + title="page3", + slug="page3", + language="en", active=True, in_navigation=True, publication_date=datetime(2001, 1, 1), @@ -660,51 +641,55 @@ def test_17_page_template_tags(self): page1 = Page.objects.get(pk=1) page2 = Page.objects.get(pk=2) - context = template.Context({'feincms_page': page2, 'page3': page3}) + context = template.Context({"feincms_page": page2, "page3": page3}) t = template.Template( - '{% load feincms_page_tags %}{% feincms_parentlink of feincms_page' - ' level=1 %}') - self.assertEqual(t.render(context), '/test-page/') + "{% load feincms_page_tags %}{% feincms_parentlink of feincms_page" + " level=1 %}" + ) + self.assertEqual(t.render(context), "/test-page/") t = template.Template( - '{% load feincms_page_tags %}{% feincms_languagelinks for' - ' feincms_page as links %}{% for key, name, link in links %}' - '{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}' - '{% endfor %}') + "{% load feincms_page_tags %}{% feincms_languagelinks for" + " feincms_page as links %}{% for key, name, link in links %}" + "{{ key }}:{{ link }}{% if not forloop.last %},{% endif %}" + "{% endfor %}" + ) self.assertEqual( - t.render(context), - 'en:/test-page/,de:/test-page/test-child-page/') + t.render(context), "en:/test-page/,de:/test-page/test-child-page/" + ) t = template.Template( - '{% load feincms_page_tags %}{% feincms_languagelinks for page3' - ' as links %}{% for key, name, link in links %}{{ key }}:' - '{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}') + "{% load feincms_page_tags %}{% feincms_languagelinks for page3" + " as links %}{% for key, name, link in links %}{{ key }}:" + "{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}" + ) self.assertEqual( - t.render(context), - 'en:/test-page/test-child-page/page3/,de:None') + t.render(context), "en:/test-page/test-child-page/page3/,de:None" + ) t = template.Template( - '{% load feincms_page_tags %}{% feincms_languagelinks for page3' - ' as links existing %}{% for key, name, link in links %}{{ key }}:' - '{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual( - t.render(context), - 'en:/test-page/test-child-page/page3/') + "{% load feincms_page_tags %}{% feincms_languagelinks for page3" + " as links existing %}{% for key, name, link in links %}{{ key }}:" + "{{ link }}{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "en:/test-page/test-child-page/page3/") t = template.Template( - '{% load feincms_page_tags %}{% feincms_languagelinks for' - ' feincms_page as links excludecurrent=1 %}' - '{% for key, name, link in links %}{{ key }}:{{ link }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), 'en:/test-page/') + "{% load feincms_page_tags %}{% feincms_languagelinks for" + " feincms_page as links excludecurrent=1 %}" + "{% for key, name, link in links %}{{ key }}:{{ link }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "en:/test-page/") t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=1 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "") # XXX should the other template tags not respect the in_navigation # setting too? @@ -712,101 +697,114 @@ def test_17_page_template_tags(self): page1.in_navigation = True page1.save() - self.assertEqual(t.render(context), '/test-page/') + self.assertEqual(t.render(context), "/test-page/") t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '/test-page/test-child-page/') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "/test-page/test-child-page/") t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav request level=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') + "{% load feincms_page_tags %}" + "{% feincms_nav request level=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) from django.http import HttpRequest + request = HttpRequest() - request.path = '/test-page/' + request.path = "/test-page/" self.assertEqual( - t.render(template.Context({'request': request})), - '/test-page/test-child-page/') + t.render(template.Context({"request": request})), + "/test-page/test-child-page/", + ) t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=99 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual(t.render(context), '') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=99 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "") t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_breadcrumbs feincms_page %}') + "{% load feincms_page_tags %}" "{% feincms_breadcrumbs feincms_page %}" + ) rendered = t.render(context) self.assertTrue("Test child page" in rendered) self.assertTrue( 'href="/test-page/">Test page' in rendered, - msg="The parent page should be a breadcrumb link") + msg="The parent page should be a breadcrumb link", + ) self.assertTrue( 'href="/test-page/test-child-page/"' not in rendered, - msg="The current page should not be a link in the breadcrumbs") + msg="The current page should not be a link in the breadcrumbs", + ) t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=2 depth=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) self.assertEqual( t.render(context), - '/test-page/test-child-page/,/test-page/test-child-page/page3/') + "/test-page/test-child-page/,/test-page/test-child-page/page3/", + ) t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=1 depth=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual( - t.render(context), - '/test-page/,/test-page/test-child-page/') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "/test-page/,/test-page/test-child-page/") t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=1 depth=3 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=3 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) self.assertEqual( t.render(context), - '/test-page/,/test-page/test-child-page/,/test-page/test-child' - '-page/page3/') + "/test-page/,/test-page/test-child-page/,/test-page/test-child" + "-page/page3/", + ) t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=3 depth=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}') - self.assertEqual( - t.render(context), '/test-page/test-child-page/page3/') + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=3 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}" + ) + self.assertEqual(t.render(context), "/test-page/test-child-page/page3/") t = template.Template( - '{% load feincms_page_tags %}' - '{% if feincms_page|is_parent_of:page3 %}yes{% endif %}|' - '{% if page3|is_parent_of:feincms_page %}yes{% endif %}') - self.assertEqual(t.render(context), 'yes|') + "{% load feincms_page_tags %}" + "{% if feincms_page|is_parent_of:page3 %}yes{% endif %}|" + "{% if page3|is_parent_of:feincms_page %}yes{% endif %}" + ) + self.assertEqual(t.render(context), "yes|") t = template.Template( - '{% load feincms_page_tags %}' - '{% if feincms_page|is_equal_or_parent_of:page3 %}yes{% endif %}|' - '{% if page3|is_equal_or_parent_of:feincms_page %}yes{% endif %}') - self.assertEqual(t.render(context), 'yes|') + "{% load feincms_page_tags %}" + "{% if feincms_page|is_equal_or_parent_of:page3 %}yes{% endif %}|" + "{% if page3|is_equal_or_parent_of:feincms_page %}yes{% endif %}" + ) + self.assertEqual(t.render(context), "yes|") t = template.Template( - '{% load feincms_page_tags %}' - '{% feincms_translatedpage for feincms_page as t1 language=de %}' - '{% feincms_translatedpage for feincms_page as t2 %}' - '{{ t1.id }}|{{ t2.id }}') - self.assertEqual(t.render(context), '2|1') + "{% load feincms_page_tags %}" + "{% feincms_translatedpage for feincms_page as t1 language=de %}" + "{% feincms_translatedpage for feincms_page as t2 %}" + "{{ t1.id }}|{{ t2.id }}" + ) + self.assertEqual(t.render(context), "2|1") def test_17_feincms_nav(self): """ @@ -815,30 +813,30 @@ def test_17_feincms_nav(self): self.login() - self.create_page_through_admin('Page 1') # 1 - self.create_page_through_admin('Page 1.1', 1) - self.create_page_through_admin('Page 1.2', 1) # 3 - self.create_page_through_admin('Page 1.2.1', 3) - self.create_page_through_admin('Page 1.2.2', 3) - self.create_page_through_admin('Page 1.2.3', 3) - self.create_page_through_admin('Page 1.3', 1) - - self.create_page_through_admin('Page 2') # 8 - self.create_page_through_admin('Page 2.1', 8) - self.create_page_through_admin('Page 2.2', 8) - self.create_page_through_admin('Page 2.3', 8) - - self.create_page_through_admin('Page 3') # 12 - self.create_page_through_admin('Page 3.1', 12) - self.create_page_through_admin('Page 3.2', 12) - self.create_page_through_admin('Page 3.3', 12) # 15 - self.create_page_through_admin('Page 3.3.1', 15) # 16 - self.create_page_through_admin('Page 3.3.1.1', 16) - self.create_page_through_admin('Page 3.3.2', 15) - - self.create_page_through_admin('Page 4') # 19 - self.create_page_through_admin('Page 4.1', 19) - self.create_page_through_admin('Page 4.2', 19) + self.create_page_through_admin("Page 1") # 1 + self.create_page_through_admin("Page 1.1", 1) + self.create_page_through_admin("Page 1.2", 1) # 3 + self.create_page_through_admin("Page 1.2.1", 3) + self.create_page_through_admin("Page 1.2.2", 3) + self.create_page_through_admin("Page 1.2.3", 3) + self.create_page_through_admin("Page 1.3", 1) + + self.create_page_through_admin("Page 2") # 8 + self.create_page_through_admin("Page 2.1", 8) + self.create_page_through_admin("Page 2.2", 8) + self.create_page_through_admin("Page 2.3", 8) + + self.create_page_through_admin("Page 3") # 12 + self.create_page_through_admin("Page 3.1", 12) + self.create_page_through_admin("Page 3.2", 12) + self.create_page_through_admin("Page 3.3", 12) # 15 + self.create_page_through_admin("Page 3.3.1", 15) # 16 + self.create_page_through_admin("Page 3.3.1.1", 16) + self.create_page_through_admin("Page 3.3.2", 15) + + self.create_page_through_admin("Page 4") # 19 + self.create_page_through_admin("Page 4.1", 19) + self.create_page_through_admin("Page 4.2", 19) """ Creates the following structure: @@ -866,122 +864,125 @@ def test_17_feincms_nav(self): tests = [ ( - {'feincms_page': Page.objects.get(pk=1)}, - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=1 depth=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13/' - ',/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/page-' - '3/page-31/,/page-3/page-32/,/page-3/page-33/', + {"feincms_page": Page.objects.get(pk=1)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13/" + ",/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/page-" + "3/page-31/,/page-3/page-32/,/page-3/page-33/", ), ( - {'feincms_page': Page.objects.get(pk=14)}, - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=2 depth=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/' - 'page-33/page-331/,/page-3/page-33/page-332/', + {"feincms_page": Page.objects.get(pk=14)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/" + "page-33/page-331/,/page-3/page-33/page-332/", ), ( - {'feincms_page': Page.objects.get(pk=14)}, - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=2 depth=3 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/' - 'page-33/page-331/,/page-3/page-33/page-331/page-3311/,/page' - '-3/page-33/page-332/', + {"feincms_page": Page.objects.get(pk=14)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=2 depth=3 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-3/page-31/,/page-3/page-32/,/page-3/page-33/,/page-3/" + "page-33/page-331/,/page-3/page-33/page-331/page-3311/,/page" + "-3/page-33/page-332/", ), ( - {'feincms_page': Page.objects.get(pk=19)}, - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=1 depth=2 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}', - '/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13' - '/,/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/pag' - 'e-3/page-31/,/page-3/page-32/,/page-3/page-33/', + {"feincms_page": Page.objects.get(pk=19)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=1 depth=2 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "/page-1/,/page-1/page-11/,/page-1/page-12/,/page-1/page-13" + "/,/page-2/,/page-2/page-22/,/page-2/page-23/,/page-3/,/pag" + "e-3/page-31/,/page-3/page-32/,/page-3/page-33/", ), ( - {'feincms_page': Page.objects.get(pk=1)}, - '{% load feincms_page_tags %}' - '{% feincms_nav feincms_page level=3 depth=1 as nav %}' - '{% for p in nav %}{{ p.get_absolute_url }}' - '{% if not forloop.last %},{% endif %}{% endfor %}', - '', + {"feincms_page": Page.objects.get(pk=1)}, + "{% load feincms_page_tags %}" + "{% feincms_nav feincms_page level=3 depth=1 as nav %}" + "{% for p in nav %}{{ p.get_absolute_url }}" + "{% if not forloop.last %},{% endif %}{% endfor %}", + "", ), ] for c, t, r in tests: - self.assertEqual( - template.Template(t).render(template.Context(c)), - r) + self.assertEqual(template.Template(t).render(template.Context(c)), r) # Test that navigation entries do not exist several times, even with # navigation extensions. Apply the PassthroughExtension to a page # which does only have direct children, because it does not collect # pages further down the tree. page = Page.objects.get(pk=8) - page.navigation_extension =\ - 'testapp.navigation_extensions.PassthroughExtension' + page.navigation_extension = "testapp.navigation_extensions.PassthroughExtension" page.save() for c, t, r in tests: - self.assertEqual( - template.Template(t).render(template.Context(c)), - r) + self.assertEqual(template.Template(t).render(template.Context(c)), r) # Now check that disabling a page also disables it in Navigation: p = Page.objects.get(pk=15) - tmpl = '''{% load feincms_page_tags %} + tmpl = """{% load feincms_page_tags %} {% feincms_nav feincms_page level=1 depth=3 as nav %} {% for p in nav %}{{ p.pk }}{% if not forloop.last %},{% endif %}{% endfor %} -''' +""" - data = template.Template(tmpl).render( - template.Context({'feincms_page': p}) - ).strip(), + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) self.assertEqual( - data, - ('1,2,3,4,6,7,8,10,11,12,13,14,15,16,18',), - "Original navigation") + data, ("1,2,3,4,6,7,8,10,11,12,13,14,15,16,18",), "Original navigation" + ) p.active = False p.save() - data = template.Template(tmpl).render( - template.Context({'feincms_page': p}) - ).strip(), + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) self.assertEqual( data, - ('1,2,3,4,6,7,8,10,11,12,13,14',), - "Navigation after disabling intermediate page") + ("1,2,3,4,6,7,8,10,11,12,13,14",), + "Navigation after disabling intermediate page", + ) # Same test with feincms_nav - tmpl = '''{% load feincms_page_tags %} + tmpl = """{% load feincms_page_tags %} {% feincms_nav feincms_page level=1 depth=3 as nav %} {% for p in nav %}{{ p.pk }}{% if not forloop.last %},{% endif %}{% endfor %} -''' +""" - data = template.Template(tmpl).render( - template.Context({'feincms_page': p}) - ).strip(), + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) self.assertEqual( data, - ('1,2,3,4,6,7,8,10,11,12,13,14',), - "Navigation after disabling intermediate page") + ("1,2,3,4,6,7,8,10,11,12,13,14",), + "Navigation after disabling intermediate page", + ) p.active = True p.save() - data = template.Template(tmpl).render( - template.Context({'feincms_page': p}) - ).strip(), + data = ( + template.Template(tmpl) + .render(template.Context({"feincms_page": p})) + .strip(), + ) self.assertEqual( - data, - ('1,2,3,4,6,7,8,10,11,12,13,14,15,16,18',), - "Original navigation") + data, ("1,2,3,4,6,7,8,10,11,12,13,14,15,16,18",), "Original navigation" + ) def test_18_default_render_method(self): """ @@ -994,17 +995,17 @@ class Meta: abstract = True def render_main(self): - return 'Hello' + return "Hello" # do not register this model in the internal FeinCMS bookkeeping # structures tmp = Page._feincms_content_types[:] - type = Page.create_content_type(Something, regions=('notexists',)) + type = Page.create_content_type(Something, regions=("notexists",)) Page._feincms_content_types = tmp - s = type(region='main', ordering='1') + s = type(region="main", ordering="1") - self.assertEqual(s.render(), 'Hello') + self.assertEqual(s.render(), "Hello") def test_19_page_manager(self): self.create_default_page_set() @@ -1015,73 +1016,78 @@ def test_19_page_manager(self): self.assertRaises( Page.DoesNotExist, - lambda: Page.objects.page_for_path(page.get_absolute_url())) + lambda: Page.objects.page_for_path(page.get_absolute_url()), + ) self.assertRaises( Page.DoesNotExist, lambda: Page.objects.best_match_for_path( - page.get_absolute_url() + 'something/hello/')) + page.get_absolute_url() + "something/hello/" + ), + ) self.assertRaises( Http404, - lambda: Page.objects.best_match_for_path( - '/blabla/blabla/', raise404=True)) + lambda: Page.objects.best_match_for_path("/blabla/blabla/", raise404=True), + ) self.assertRaises( - Http404, - lambda: Page.objects.page_for_path('/asdf/', raise404=True)) + Http404, lambda: Page.objects.page_for_path("/asdf/", raise404=True) + ) self.assertRaises( Page.DoesNotExist, - lambda: Page.objects.best_match_for_path('/blabla/blabla/')) + lambda: Page.objects.best_match_for_path("/blabla/blabla/"), + ) self.assertRaises( - Page.DoesNotExist, - lambda: Page.objects.page_for_path('/asdf/')) + Page.DoesNotExist, lambda: Page.objects.page_for_path("/asdf/") + ) request = Empty() request.path = request.path_info = page.get_absolute_url() - request.method = 'GET' - request.get_full_path = lambda: '/xyz/' + request.method = "GET" + request.get_full_path = lambda: "/xyz/" request.GET = {} request.META = {} request.user = AnonymousUser() # tadaa from django.utils import translation + translation.activate(page.language) page.active = False page.save() self.assertRaises( - Http404, - lambda: Page.objects.for_request(request, raise404=True)) + Http404, lambda: Page.objects.for_request(request, raise404=True) + ) page.active = True page.save() self.assertRaises( - Http404, - lambda: Page.objects.for_request(request, raise404=True)) + Http404, lambda: Page.objects.for_request(request, raise404=True) + ) page.parent.active = True page.parent.save() self.assertEqual(page, Page.objects.for_request(request)) - self.assertEqual( - page, Page.objects.page_for_path(page.get_absolute_url())) + self.assertEqual(page, Page.objects.page_for_path(page.get_absolute_url())) self.assertEqual( page, Page.objects.best_match_for_path( - page.get_absolute_url() + 'something/hello/')) + page.get_absolute_url() + "something/hello/" + ), + ) old = feincms_settings.FEINCMS_ALLOW_EXTRA_PATH - request.path += 'hello/' + request.path += "hello/" feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = False self.assertEqual(self.client.get(request.path).status_code, 404) feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = True self.assertEqual(self.client.get(request.path).status_code, 200) - self.assertEqual( - page, Page.objects.for_request(request, best_match=True)) + self.assertEqual(page, Page.objects.for_request(request, best_match=True)) feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = old @@ -1096,7 +1102,7 @@ def test_20_redirects(self): page2.active = True page2.publication_date = timezone.now() - timedelta(days=1) - page2.override_url = '/blablabla/' + page2.override_url = "/blablabla/" page2.redirect_to = page1.get_absolute_url() page2.save() @@ -1109,11 +1115,11 @@ def test_20_redirects(self): # page2 has been modified too, but its URL should not have changed try: self.assertRedirects( - self.client.get('/blablabla/'), - page1.get_absolute_url()) + self.client.get("/blablabla/"), page1.get_absolute_url() + ) except TemplateDoesNotExist as e: # catch the error from rendering page1 - if e.args != ('feincms_base.html',): + if e.args != ("feincms_base.html",): raise def test_21_copy_content(self): @@ -1133,8 +1139,7 @@ def test_23_navigation_extension(self): self.assertEqual(len(page.extended_navigation()), 0) - page.navigation_extension =\ - 'testapp.navigation_extensions.PassthroughExtension' + page.navigation_extension = "testapp.navigation_extensions.PassthroughExtension" page2 = Page.objects.get(pk=2) page2.active = True @@ -1143,16 +1148,15 @@ def test_23_navigation_extension(self): self.assertEqual(list(page.extended_navigation()), [page2]) - page.navigation_extension =\ - 'testapp.navigation_extensions.ThisExtensionDoesNotExist' + page.navigation_extension = ( + "testapp.navigation_extensions.ThisExtensionDoesNotExist" + ) self.assertEqual(len(page.extended_navigation()), 1) - page.navigation_extension =\ - 'testapp.navigation_extensions.PretenderExtension' + page.navigation_extension = "testapp.navigation_extensions.PretenderExtension" - self.assertEqual( - page.extended_navigation()[0].get_absolute_url(), '/asdsa/') + self.assertEqual(page.extended_navigation()[0].get_absolute_url(), "/asdsa/") def test_24_admin_redirects(self): self.create_default_page_set() @@ -1160,15 +1164,13 @@ def test_24_admin_redirects(self): page = Page.objects.get(pk=1) response = self.create_page_through_admincontent(page, _continue=1) - self.assertRedirects( - response, - reverse('admin:page_page_change', args=(1,))) + self.assertRedirects(response, reverse("admin:page_page_change", args=(1,))) response = self.create_page_through_admincontent(page, _addanother=1) - self.assertRedirects(response, '/admin/page/page/add/') + self.assertRedirects(response, "/admin/page/page/add/") response = self.create_page_through_admincontent(page) - self.assertRedirects(response, '/admin/page/page/') + self.assertRedirects(response, "/admin/page/page/") def test_25_applicationcontent(self): self.create_default_page_set() @@ -1179,134 +1181,131 @@ def test_25_applicationcontent(self): page = Page.objects.get(pk=2) page.active = True - page.template_key = 'theother' + page.template_key = "theother" page.save() # Should not be published because the page has no application contents # and should therefore not catch anything below it. - self.is_published(page1.get_absolute_url() + 'anything/', False) + self.is_published(page1.get_absolute_url() + "anything/", False) page.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='testapp.applicationcontent_urls') + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) + self.assertContains(self.client.get(page.get_absolute_url()), "module_root") self.assertContains( - self.client.get(page.get_absolute_url()), - 'module_root') - self.assertContains( - self.client.get(page.get_absolute_url() + 'args_test/abc/def/'), - 'abc-def') + self.client.get(page.get_absolute_url() + "args_test/abc/def/"), "abc-def" + ) self.assertContains( - self.client.get(page.get_absolute_url() + 'kwargs_test/abc/def/'), - 'def-abc') + self.client.get(page.get_absolute_url() + "kwargs_test/abc/def/"), "def-abc" + ) - response = self.client.get( - page.get_absolute_url() + 'full_reverse_test/') - self.assertContains(response, 'home:/test-page/test-child-page/') + response = self.client.get(page.get_absolute_url() + "full_reverse_test/") + self.assertContains(response, "home:/test-page/test-child-page/") self.assertContains( - response, - 'args:/test-page/test-child-page/args_test/xy/zzy/') - self.assertContains(response, 'base:/test/') - self.assertContains(response, 'homeas:/test-page/test-child-page/') + response, "args:/test-page/test-child-page/args_test/xy/zzy/" + ) + self.assertContains(response, "base:/test/") + self.assertContains(response, "homeas:/test-page/test-child-page/") self.assertEqual( - app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), - '/test-page/test-child-page/') + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + "/test-page/test-child-page/", + ) - if hasattr(self, 'assertNumQueries'): + if hasattr(self, "assertNumQueries"): self.assertNumQueries( 0, lambda: app_reverse( - 'ac_module_root', 'testapp.applicationcontent_urls')) + "ac_module_root", "testapp.applicationcontent_urls" + ), + ) # This should not raise self.assertEqual( - self.client.get( - page.get_absolute_url() + 'notexists/' - ).status_code, 404) + self.client.get(page.get_absolute_url() + "notexists/").status_code, 404 + ) self.assertContains( - self.client.get(page.get_absolute_url() + 'fragment/'), - 'some things') + self.client.get(page.get_absolute_url() + "fragment/"), + 'some things', + ) self.assertRedirects( - self.client.get(page.get_absolute_url() + 'redirect/'), - 'http://testserver' + page.get_absolute_url()) + self.client.get(page.get_absolute_url() + "redirect/"), + "http://testserver" + page.get_absolute_url(), + ) self.assertEqual( - app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), - page.get_absolute_url()) + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page.get_absolute_url(), + ) - response = self.client.get(page.get_absolute_url() + 'response/') - self.assertContains(response, 'Anything') + response = self.client.get(page.get_absolute_url() + "response/") + self.assertContains(response, "Anything") # Ensure response has been wrapped - self.assertContains(response, '

Main content

') + self.assertContains(response, "

Main content

") # Test standalone behavior self.assertEqual( self.client.get( - page.get_absolute_url() + 'response/', - HTTP_X_REQUESTED_WITH='XMLHttpRequest').content, - self.client.get( - page.get_absolute_url() + 'response_decorated/').content) + page.get_absolute_url() + "response/", + HTTP_X_REQUESTED_WITH="XMLHttpRequest", + ).content, + self.client.get(page.get_absolute_url() + "response_decorated/").content, + ) page1.applicationcontent_set.create( - region='main', - ordering=0, - urlconf_path='whatever') + region="main", ordering=0, urlconf_path="whatever" + ) - response = self.client.get( - page.get_absolute_url() + 'alias_reverse_test/') - self.assertContains(response, 'home:/test-page/') - self.assertContains(response, 'args:/test-page/args_test/xy/zzy/') - self.assertContains(response, 'base:/test/') + response = self.client.get(page.get_absolute_url() + "alias_reverse_test/") + self.assertContains(response, "home:/test-page/") + self.assertContains(response, "args:/test-page/args_test/xy/zzy/") + self.assertContains(response, "base:/test/") self.assertEqual( - app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), - '/test-page/test-child-page/') - self.assertEqual( - app_reverse('ac_module_root', 'whatever'), - '/test-page/') + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + "/test-page/test-child-page/", + ) + self.assertEqual(app_reverse("ac_module_root", "whatever"), "/test-page/") page.applicationcontent_set.get( - urlconf_path='testapp.applicationcontent_urls').delete() + urlconf_path="testapp.applicationcontent_urls" + ).delete() - self.assertEqual( - app_reverse('ac_module_root', 'whatever'), - '/test-page/') + self.assertEqual(app_reverse("ac_module_root", "whatever"), "/test-page/") # Ensure ApplicationContent's admin_fields support works properly self.login() - response = self.client.get( - reverse('admin:page_page_change', args=(page1.id,)) - ) + response = self.client.get(reverse("admin:page_page_change", args=(page1.id,))) - self.assertContains(response, 'exclusive_subpages') - self.assertContains(response, 'custom_field') + self.assertContains(response, "exclusive_subpages") + self.assertContains(response, "custom_field") # Check if admin_fields get populated correctly app_ct = page1.applicationcontent_set.all()[0] - app_ct.parameters =\ - '{"custom_field":"val42", "exclusive_subpages": false}' + app_ct.parameters = '{"custom_field":"val42", "exclusive_subpages": false}' app_ct.save() - response = self.client.get( - reverse('admin:page_page_change', args=(page1.id,)) - ) - self.assertContains(response, 'val42') + response = self.client.get(reverse("admin:page_page_change", args=(page1.id,))) + self.assertContains(response, "val42") def test_26_page_form_initial(self): self.create_default_page_set() self.login() - self.assertEqual(self.client.get( - '/admin/page/page/add/?translation_of=1&lang=de' - ).status_code, 200) - self.assertEqual(self.client.get( - '/admin/page/page/add/?parent=1' - ).status_code, 200) - self.assertEqual(self.client.get( - '/admin/page/page/add/?parent=2' - ).status_code, 200) + self.assertEqual( + self.client.get( + "/admin/page/page/add/?translation_of=1&lang=de" + ).status_code, + 200, + ) + self.assertEqual( + self.client.get("/admin/page/page/add/?parent=1").status_code, 200 + ) + self.assertEqual( + self.client.get("/admin/page/page/add/?parent=2").status_code, 200 + ) def test_27_cached_url_clash(self): self.create_default_page_set() @@ -1314,15 +1313,15 @@ def test_27_cached_url_clash(self): page1 = Page.objects.get(pk=1) page2 = Page.objects.get(pk=2) - page1.override_url = '/' + page1.override_url = "/" page1.active = True page1.save() self.login() self.assertContains( - self.create_page_through_admincontent( - page2, active=True, override_url='/'), - 'already taken by') + self.create_page_through_admincontent(page2, active=True, override_url="/"), + "already taken by", + ) def test_28_applicationcontent_reverse(self): self.create_default_page_set() @@ -1332,48 +1331,49 @@ def test_28_applicationcontent_reverse(self): page = Page.objects.get(pk=2) page.active = True - page.template_key = 'theother' + page.template_key = "theother" page.save() page.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='testapp.applicationcontent_urls') + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) # test app_reverse self.assertEqual( - app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), - page.get_absolute_url()) + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page.get_absolute_url(), + ) # when specific applicationcontent exists more then once reverse should # return the URL of the first (ordered by primary key) page. self.login() + self.create_page_through_admin(title="Home DE", language="de", active=True) + page_de = Page.objects.get(title="Home DE") self.create_page_through_admin( - title='Home DE', language='de', active=True) - page_de = Page.objects.get(title='Home DE') - self.create_page_through_admin( - title='Child 1 DE', language='de', parent=page_de.id, active=True) - page_de_1 = Page.objects.get(title='Child 1 DE') + title="Child 1 DE", language="de", parent=page_de.id, active=True + ) + page_de_1 = Page.objects.get(title="Child 1 DE") page_de_1.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='testapp.applicationcontent_urls') + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) page.active = False page.save() - settings.TEMPLATE_DIRS = ( - os.path.join(os.path.dirname(__file__), 'templates'), - ) + settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), "templates"),) self.client.get(page_de_1.get_absolute_url()) self.assertEqual( - app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), - page_de_1.get_absolute_url()) + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page_de_1.get_absolute_url(), + ) page.active = True page.save() self.client.get(page1.get_absolute_url()) self.assertEqual( - app_reverse('ac_module_root', 'testapp.applicationcontent_urls'), - page.get_absolute_url()) + app_reverse("ac_module_root", "testapp.applicationcontent_urls"), + page.get_absolute_url(), + ) def test_29_medialibrary_admin(self): self.create_default_page_set() @@ -1381,56 +1381,59 @@ def test_29_medialibrary_admin(self): page = Page.objects.get(pk=1) - mediafile = MediaFile.objects.create(file='somefile.jpg') + mediafile = MediaFile.objects.create(file="somefile.jpg") page.mediafilecontent_set.create( - mediafile=mediafile, - region='main', - type='default', - ordering=1) + mediafile=mediafile, region="main", type="default", ordering=1 + ) self.assertContains( - self.client.get('/admin/medialibrary/mediafile/'), - 'somefile.jpg') + self.client.get("/admin/medialibrary/mediafile/"), "somefile.jpg" + ) import zipfile - zf = zipfile.ZipFile('test.zip', 'w') + + zf = zipfile.ZipFile("test.zip", "w") for i in range(10): - zf.writestr('test%d.txt' % i, 'test%d' % i) + zf.writestr("test%d.txt" % i, "test%d" % i) zf.close() - with open('test.zip', 'rb') as handle: + with open("test.zip", "rb") as handle: response = self.client.post( - '/admin/medialibrary/mediafile/mediafile-bulk-upload/', { - 'data': handle, - }) - self.assertRedirects(response, '/admin/medialibrary/mediafile/') + "/admin/medialibrary/mediafile/mediafile-bulk-upload/", {"data": handle} + ) + self.assertRedirects(response, "/admin/medialibrary/mediafile/") self.assertEqual( MediaFile.objects.count(), 11, - "Upload of media files with ZIP does not work") + "Upload of media files with ZIP does not work", + ) dn = os.path.dirname path = os.path.join( - dn(dn(dn(dn(__file__)))), 'docs', 'images', 'tree_editor.png') + dn(dn(dn(dn(__file__)))), "docs", "images", "tree_editor.png" + ) - with open(path, 'rb') as handle: - response = self.client.post('/admin/medialibrary/mediafile/add/', { - 'file': handle, - 'translations-TOTAL_FORMS': 0, - 'translations-INITIAL_FORMS': 0, - 'translations-MAX_NUM_FORMS': 10, - }) - self.assertRedirects(response, '/admin/medialibrary/mediafile/') + with open(path, "rb") as handle: + response = self.client.post( + "/admin/medialibrary/mediafile/add/", + { + "file": handle, + "translations-TOTAL_FORMS": 0, + "translations-INITIAL_FORMS": 0, + "translations-MAX_NUM_FORMS": 10, + }, + ) + self.assertRedirects(response, "/admin/medialibrary/mediafile/") self.assertContains( - self.client.get('/admin/medialibrary/mediafile/'), - '100x100') + self.client.get("/admin/medialibrary/mediafile/"), "100x100" + ) - stats = list(MediaFile.objects.values_list('type', flat=True)) + stats = list(MediaFile.objects.values_list("type", flat=True)) self.assertEqual(len(stats), 12) - self.assertEqual(stats.count('image'), 2) - self.assertEqual(stats.count('txt'), 10) + self.assertEqual(stats.count("image"), 2) + self.assertEqual(stats.count("txt"), 10) def test_30_context_processors(self): self.create_default_page_set() @@ -1439,20 +1442,20 @@ def test_30_context_processors(self): request = Empty() request.GET = {} request.META = {} - request.method = 'GET' - request.path = request.path_info = '/test-page/test-child-page/abcdef/' - request.get_full_path = lambda: '/test-page/test-child-page/abcdef/' + request.method = "GET" + request.path = request.path_info = "/test-page/test-child-page/abcdef/" + request.get_full_path = lambda: "/test-page/test-child-page/abcdef/" ctx = add_page_if_missing(request) - self.assertEqual(ctx['feincms_page'], request._feincms_page) + self.assertEqual(ctx["feincms_page"], request._feincms_page) def test_31_sites_framework_associating_with_single_site(self): self.login() - site_2 = Site.objects.create(name='site 2', domain='2.example.com') - self.create_page_through_admin( - 'site 1 homepage', override_url='/', active=True) + site_2 = Site.objects.create(name="site 2", domain="2.example.com") + self.create_page_through_admin("site 1 homepage", override_url="/", active=True) self.create_page_through_admin( - 'site 2 homepage', override_url='/', site=site_2.id, active=True) + "site 2 homepage", override_url="/", site=site_2.id, active=True + ) self.assertEqual(Page.objects.count(), 2) self.assertEqual(Page.objects.active().count(), 1) @@ -1465,145 +1468,141 @@ def test_32_applicationcontent_inheritance20(self): page = Page.objects.get(pk=2) page.active = True - page.template_key = 'theother' + page.template_key = "theother" page.save() # Should not be published because the page has no application contents # and should therefore not catch anything below it. - self.is_published(page1.get_absolute_url() + 'anything/', False) + self.is_published(page1.get_absolute_url() + "anything/", False) page.applicationcontent_set.create( - region='main', ordering=0, - urlconf_path='testapp.applicationcontent_urls') + region="main", ordering=0, urlconf_path="testapp.applicationcontent_urls" + ) page.rawcontent_set.create( - region='main', ordering=1, text='some_main_region_text') + region="main", ordering=1, text="some_main_region_text" + ) page.rawcontent_set.create( - region='sidebar', ordering=0, text='some_sidebar_region_text') + region="sidebar", ordering=0, text="some_sidebar_region_text" + ) - self.assertContains(self.client.get(page.get_absolute_url()), - 'module_root') + self.assertContains(self.client.get(page.get_absolute_url()), "module_root") - response = self.client.get(page.get_absolute_url() + 'inheritance20/') - self.assertContains(response, 'a content 42') - self.assertContains(response, 'b content') - self.assertNotContains(response, 'some_main_region_text') - self.assertContains(response, 'some_sidebar_region_text') - self.assertNotContains(response, 'some content outside') + response = self.client.get(page.get_absolute_url() + "inheritance20/") + self.assertContains(response, "a content 42") + self.assertContains(response, "b content") + self.assertNotContains(response, "some_main_region_text") + self.assertContains(response, "some_sidebar_region_text") + self.assertNotContains(response, "some content outside") - response = self.client.get( - page.get_absolute_url() + 'inheritance20_unpack/') - self.assertContains(response, 'a content 43') - self.assertIn('yabba dabba', response['cache-control']) + response = self.client.get(page.get_absolute_url() + "inheritance20_unpack/") + self.assertContains(response, "a content 43") + self.assertIn("yabba dabba", response["cache-control"]) def test_33_preview(self): self.create_default_page_set() page = Page.objects.get(pk=1) - page.template_key = 'theother' + page.template_key = "theother" page.save() - page.rawcontent_set.create( - region='main', - ordering=0, - text='Example content') + page.rawcontent_set.create(region="main", ordering=0, text="Example content") self.login() - self.assertEqual( - self.client.get(page.get_absolute_url()).status_code, 404) + self.assertEqual(self.client.get(page.get_absolute_url()).status_code, 404) self.assertContains( - self.client.get('%s_preview/%s/' % ( - page.get_absolute_url(), - page.pk), - ), - 'Example content') + self.client.get("%s_preview/%s/" % (page.get_absolute_url(), page.pk)), + "Example content", + ) def test_34_access(self): self.create_default_page_set() page = Page.objects.get(pk=1) - page.override_url = '/something/' + page.override_url = "/something/" page.save() Page.objects.update(active=True) self.login() self.create_page_through_admin( - title='redirect page', - override_url='/', + title="redirect page", + override_url="/", redirect_to=page.get_absolute_url(), - active=True) + active=True, + ) # / -> redirect to /something/ - r = self.client.get('/') + r = self.client.get("/") self.assertRedirects(r, page.get_absolute_url()) # /something/ should work r = self.client.get(page.override_url) self.assertEqual(r.status_code, 200) # /foo not existant -> 404 - r = self.client.get('/foo/') + r = self.client.get("/foo/") self.assertEqual(r.status_code, 404) def test_35_access_with_extra_path(self): self.login() self.create_page( - title='redirect again', - override_url='/', - redirect_to='/somewhere/', - active=True) - self.create_page(title='somewhere', active=True) - - r = self.client.get('/') - self.assertRedirects(r, '/somewhere/') - r = self.client.get('/dingdong/') + title="redirect again", + override_url="/", + redirect_to="/somewhere/", + active=True, + ) + self.create_page(title="somewhere", active=True) + + r = self.client.get("/") + self.assertRedirects(r, "/somewhere/") + r = self.client.get("/dingdong/") self.assertEqual(r.status_code, 404) old = feincms_settings.FEINCMS_ALLOW_EXTRA_PATH feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = True - r = self.client.get('/') - self.assertRedirects(r, '/somewhere/') - r = self.client.get('/dingdong/') + r = self.client.get("/") + self.assertRedirects(r, "/somewhere/") + r = self.client.get("/dingdong/") self.assertEqual(r.status_code, 404) feincms_settings.FEINCMS_ALLOW_EXTRA_PATH = old def test_36_sitemaps(self): - response = self.client.get('/sitemap.xml') - self.assertContains(response, '', status_code=200) + response = self.client.get("/sitemap.xml") + self.assertNotContains(response, "", status_code=200) page = Page.objects.get() page.active = True page.in_navigation = True page.save() - response = self.client.get('/sitemap.xml') - self.assertContains(response, '', status_code=200) + response = self.client.get("/sitemap.xml") + self.assertContains(response, "", status_code=200) def test_37_invalid_parent(self): self.create_default_page_set() - page1, page2 = list(Page.objects.order_by('id')) + page1, page2 = list(Page.objects.order_by("id")) page1.parent = page1 self.assertRaises(InvalidMove, page1.save) - self.create_page('Page 3', parent=page2) - page1, page2, page3 = list(Page.objects.order_by('id')) + self.create_page("Page 3", parent=page2) + page1, page2, page3 = list(Page.objects.order_by("id")) page1.parent = page3 self.assertRaises(InvalidMove, page1.save) def test_38_invalid_template(self): page = Page() - page.template_key = 'test' - self.assertEqual(page.template.key, 'base') + page.template_key = "test" + self.assertEqual(page.template.key, "base") def test_39_navigationgroups(self): self.create_default_page_set() - page1, page2 = list(Page.objects.order_by('id')) + page1, page2 = list(Page.objects.order_by("id")) page1.active = True page1.in_navigation = True @@ -1611,84 +1610,77 @@ def test_39_navigationgroups(self): page2.active = True page2.in_navigation = True - page2.navigation_group = 'footer' + page2.navigation_group = "footer" page2.save() t = template.Template( - ''' + """ {% load feincms_page_tags %} {% feincms_nav feincms_page level=1 depth=10 group='default' as nav %} {% for p in nav %}{{ p.get_absolute_url }},{% endfor %} - ''' + """ ) - str = t.render(template.Context({ - 'feincms_page': page1, - })) + str = t.render(template.Context({"feincms_page": page1})) - self.assertEqual(str.strip(), '/test-page/,') + self.assertEqual(str.strip(), "/test-page/,") t = template.Template( - ''' + """ {% load feincms_page_tags %} {% feincms_nav feincms_page level=1 depth=10 group='footer' as nav %} {% for p in nav %}{{ p.get_absolute_url }},{% endfor %} - ''' + """ ) - str = t.render(template.Context({ - 'feincms_page': page1, - })) + str = t.render(template.Context({"feincms_page": page1})) - self.assertEqual(str.strip(), '/test-page/test-child-page/,') + self.assertEqual(str.strip(), "/test-page/test-child-page/,") def test_40_page_is_active(self): self.create_default_page_set() - page1, page2 = list(Page.objects.order_by('id')) + page1, page2 = list(Page.objects.order_by("id")) - self.assertTrue(feincms_page_tags.page_is_active( - {'feincms_page': page1}, page1)) - self.assertTrue(feincms_page_tags.page_is_active( - {'feincms_page': page2}, page1)) - self.assertFalse(feincms_page_tags.page_is_active( - {'feincms_page': page1}, page2)) + self.assertTrue( + feincms_page_tags.page_is_active({"feincms_page": page1}, page1) + ) + self.assertTrue( + feincms_page_tags.page_is_active({"feincms_page": page2}, page1) + ) + self.assertFalse( + feincms_page_tags.page_is_active({"feincms_page": page1}, page2) + ) - p = PagePretender( - title='bla', - slug='bla', - url='/test-page/whatsup/') + p = PagePretender(title="bla", slug="bla", url="/test-page/whatsup/") - self.assertTrue(feincms_page_tags.page_is_active( - {}, p, path='/test-page/whatsup/test/')) - self.assertFalse(feincms_page_tags.page_is_active( - {}, p, path='/test-page/')) + self.assertTrue( + feincms_page_tags.page_is_active({}, p, path="/test-page/whatsup/test/") + ) + self.assertFalse(feincms_page_tags.page_is_active({}, p, path="/test-page/")) - self.assertTrue(feincms_page_tags.page_is_active( - {'feincms_page': page1}, p, path='/test-page/whatsup/test/')) - self.assertFalse(feincms_page_tags.page_is_active( - {'feincms_page': page2}, p, path='/test-page/')) + self.assertTrue( + feincms_page_tags.page_is_active( + {"feincms_page": page1}, p, path="/test-page/whatsup/test/" + ) + ) + self.assertFalse( + feincms_page_tags.page_is_active( + {"feincms_page": page2}, p, path="/test-page/" + ) + ) def test_41_templatecontent(self): page = self.create_page(active=True) page.templatecontent_set.create( - region='main', - ordering=10, - template='templatecontent_1.html', + region="main", ordering=10, template="templatecontent_1.html" ) # The empty form contains the template option. self.login() self.assertContains( - self.client.get( - reverse('admin:page_page_change', args=(page.id,)) - ), - '') + self.client.get(reverse("admin:page_page_change", args=(page.id,))), + '', + ) response = self.client.get(page.get_absolute_url()) - self.assertContains( - response, - 'TemplateContent_1', - ) - self.assertContains( - response, - '#42#', - ) + self.assertContains(response, "TemplateContent_1") + self.assertContains(response, "#42#") diff --git a/tests/testapp/tests/test_stuff.py b/tests/testapp/tests/test_stuff.py index b567daba6..71ac0bd4e 100644 --- a/tests/testapp/tests/test_stuff.py +++ b/tests/testapp/tests/test_stuff.py @@ -39,47 +39,40 @@ class ModelsTest(TestCase): def test_region(self): # Creation should not fail - r = Region('region', 'region title') + r = Region("region", "region title") t = Template( - 'base template', - 'base.html', - ( - ('region', 'region title'), - Region('region2', 'region2 title'), - ), + "base template", + "base.html", + (("region", "region title"), Region("region2", "region2 title")), ) # I'm not sure whether this test tests anything at all self.assertEqual(r.key, t.regions[0].key) - self.assertEqual(force_text(r), 'region title') + self.assertEqual(force_text(r), "region title") class UtilsTest(TestCase): def test_get_object(self): - self.assertRaises( - AttributeError, lambda: get_object('feincms.does_not_exist')) - self.assertRaises( - ImportError, lambda: get_object('feincms.does_not_exist.fn')) + self.assertRaises(AttributeError, lambda: get_object("feincms.does_not_exist")) + self.assertRaises(ImportError, lambda: get_object("feincms.does_not_exist.fn")) - self.assertEqual(get_object, get_object('feincms.utils.get_object')) + self.assertEqual(get_object, get_object("feincms.utils.get_object")) def test_shorten_string(self): string = shorten_string( - "Der Wolf und die Grossmutter assen im Wald zu mittag", - 15, ellipsis="_") - self.assertEqual(string, 'Der Wolf und_ag') + "Der Wolf und die Grossmutter assen im Wald zu mittag", 15, ellipsis="_" + ) + self.assertEqual(string, "Der Wolf und_ag") self.assertEqual(len(string), 15) string = shorten_string( - "Haenschen-Klein, ging allein, in den tiefen Wald hinein", - 15) - self.assertEqual(string, 'Haenschen \u2026 ein') + "Haenschen-Klein, ging allein, in den tiefen Wald hinein", 15 + ) + self.assertEqual(string, "Haenschen \u2026 ein") self.assertEqual(len(string), 15) - string = shorten_string( - 'Badgerbadgerbadgerbadgerbadger', - 10, ellipsis='-') - self.assertEqual(string, 'Badger-ger') + string = shorten_string("Badgerbadgerbadgerbadgerbadger", 10, ellipsis="-") + self.assertEqual(string, "Badger-ger") self.assertEqual(len(string), 10) @@ -88,7 +81,7 @@ class TimezoneTest(TestCase): def test_granular_now_dst_transition(self): # Should not raise an exception d = datetime(2016, 10, 30, 2, 10) - tz = pytz.timezone('Europe/Copenhagen') + tz = pytz.timezone("Europe/Copenhagen") granular_now(d, default_tz=tz) def test_granular_now_rounding(self): @@ -97,4 +90,5 @@ def test_granular_now_rounding(self): self.assertEqual(d.hour, g.hour) self.assertEqual(10, g.minute) + # ------------------------------------------------------------------------ diff --git a/tests/testapp/tests/utils.py b/tests/testapp/tests/utils.py index 31ca0639e..5d52b4f5f 100644 --- a/tests/testapp/tests/utils.py +++ b/tests/testapp/tests/utils.py @@ -8,6 +8,7 @@ class MockDatetime(datetime.datetime): @classmethod def now(cls): return datetime.datetime(2012, 6, 1) + return MockDatetime @@ -16,4 +17,5 @@ class MockDate(datetime.date): @classmethod def today(cls): return datetime.date(2012, 6, 1) + return MockDate diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py index f1dbcc4a9..df1d6791a 100644 --- a/tests/testapp/urls.py +++ b/tests/testapp/urls.py @@ -11,27 +11,20 @@ from feincms.module.page.sitemap import PageSitemap -sitemaps = {'pages': PageSitemap} +sitemaps = {"pages": PageSitemap} admin.autodiscover() urlpatterns = [ - url(r'^admin/', admin.site.urls), - + url(r"^admin/", admin.site.urls), url( - r'^media/(?P.*)$', + r"^media/(?P.*)$", serve, - {'document_root': os.path.join(os.path.dirname(__file__), 'media/')}, + {"document_root": os.path.join(os.path.dirname(__file__), "media/")}, ), - - url( - r'^sitemap\.xml$', - sitemap, - {'sitemaps': sitemaps}, - ), - - url(r'', include('feincms.contrib.preview.urls')), - url(r'', include('feincms.urls')), + url(r"^sitemap\.xml$", sitemap, {"sitemaps": sitemaps}), + url(r"", include("feincms.contrib.preview.urls")), + url(r"", include("feincms.urls")), ] urlpatterns += staticfiles_urlpatterns() diff --git a/tox.ini b/tox.ini index 3f96dd417..cf21779ef 100644 --- a/tox.ini +++ b/tox.ini @@ -3,9 +3,11 @@ basepython = python3 [testenv:style] deps = + black flake8 changedir = {toxinidir} commands = + black feincms tests setup.py flake8 . skip_install = true From 8c94fb0a429d90b5557aa5649e351d3922d92c48 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 1 Feb 2019 18:28:05 +0100 Subject: [PATCH 487/654] Django@master dropped the staticfiles template tag library --- feincms/module/page/modeladmins.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/feincms/module/page/modeladmins.py b/feincms/module/page/modeladmins.py index 4a6847537..fe2050daf 100644 --- a/feincms/module/page/modeladmins.py +++ b/feincms/module/page/modeladmins.py @@ -9,7 +9,6 @@ from django.conf import settings as django_settings from django.core.exceptions import PermissionDenied from django.contrib.contenttypes.models import ContentType -from django.contrib.staticfiles.templatetags.staticfiles import static from django.contrib import admin from django.contrib import messages from django.http import HttpResponseRedirect @@ -21,6 +20,12 @@ except ImportError: from django.core.urlresolvers import reverse +try: + from django.contrib.staticfiles.templatetags.staticfiles import static +except ImportError: + # Newer Django versions. + from django.templatetags.static import static + from feincms import ensure_completely_loaded from feincms import settings from feincms.admin import item_editor, tree_editor From 54a4a2eb32763c8ed2b9ddb66cb92195564edf86 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 1 Feb 2019 18:31:53 +0100 Subject: [PATCH 488/654] FeinCMS v1.16.0 --- CHANGELOG.rst | 8 +++++++- feincms/__init__.py | 2 +- setup.py | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5a54bc50d..11a949cb4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,12 @@ Change log `Next version`_ ~~~~~~~~~~~~~~~ +`v1.16.0`_ (2019-02-01) +~~~~~~~~~~~~~~~~~~~~~~~ + - Reformatted everything using black. +- Added a fallback import for the ``staticfiles`` template tag library + which will be gone in Django 3.0. `v1.15.0`_ (2018-12-21) @@ -59,4 +64,5 @@ Change log .. _v1.14.0: https://github.com/feincms/feincms/compare/v1.13.0...v1.14.0 .. _v1.15.0: https://github.com/feincms/feincms/compare/v1.14.0...v1.15.0 -.. _Next version: https://github.com/feincms/feincms/compare/v1.15.0...master +.. _v1.16.0: https://github.com/feincms/feincms/compare/v1.15.0...v1.16.0 +.. _Next version: https://github.com/feincms/feincms/compare/v1.16.0...master diff --git a/feincms/__init__.py b/feincms/__init__.py index 308b8f1cd..68ac06ad5 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, unicode_literals -VERSION = (1, 15, 0) +VERSION = (1, 16, 0) __version__ = ".".join(map(str, VERSION)) diff --git a/setup.py b/setup.py index a9fdce423..f969f4cd6 100755 --- a/setup.py +++ b/setup.py @@ -49,6 +49,7 @@ def read(filename): "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development", "Topic :: Software Development :: Libraries :: Application Frameworks", From 17de1d0371bd491954bd26c4ddacf555467353e2 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Fri, 1 Feb 2019 18:42:45 +0100 Subject: [PATCH 489/654] Various compatibility fixes --- feincms/admin/tree_editor.py | 7 ++++++- feincms/contrib/fields.py | 2 +- feincms/module/medialibrary/models.py | 2 +- feincms/templates/admin/feincms/_regions_js.html | 2 +- feincms/templates/admin/feincms/item_editor.html | 2 +- feincms/templates/admin/feincms/load-jquery.include | 2 +- feincms/templates/admin/feincms/tree_editor.html | 2 +- tests/testapp/settings.py | 2 +- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index 2eecb1484..9e718b714 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -11,7 +11,6 @@ from django.contrib.admin.views import main from django.contrib.admin.actions import delete_selected from django.contrib.auth import get_permission_codename -from django.contrib.staticfiles.templatetags.staticfiles import static from django.db.models import Q from django.http import ( HttpResponse, @@ -31,6 +30,12 @@ from feincms import settings from feincms.extensions import ExtensionModelAdmin +try: + # Django<3 + from django.contrib.staticfiles.templatetags.staticfiles import static +except ImportError: + from django.templatetags.static import static + logger = logging.getLogger(__name__) diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index bf8b8ed3b..89f0b2639 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -69,7 +69,7 @@ def to_python(self, value): assert value is None return {} - def from_db_value(self, value, expression, connection, context): + def from_db_value(self, value, expression, connection, context=None): return self.to_python(value) def get_prep_value(self, value): diff --git a/feincms/module/medialibrary/models.py b/feincms/module/medialibrary/models.py index 424abd327..1a2d2ba08 100644 --- a/feincms/module/medialibrary/models.py +++ b/feincms/module/medialibrary/models.py @@ -143,7 +143,7 @@ def register_filetypes(cls, *types): cls.filetypes[0:0] = types choices = [t[0:2] for t in cls.filetypes] cls.filetypes_dict = dict(choices) - cls._meta.get_field("type").choices[:] = choices + cls._meta.get_field("type").choices = choices def __init__(self, *args, **kwargs): super(MediaFileBase, self).__init__(*args, **kwargs) diff --git a/feincms/templates/admin/feincms/_regions_js.html b/feincms/templates/admin/feincms/_regions_js.html index adebda187..2d39f7eb3 100644 --- a/feincms/templates/admin/feincms/_regions_js.html +++ b/feincms/templates/admin/feincms/_regions_js.html @@ -1,4 +1,4 @@ -{% load staticfiles %} +{% load static %} {% endblock %} From 7687efc7b2362b00a63dbda8c40cbfa5b9875348 Mon Sep 17 00:00:00 2001 From: Arkadiy Korotaev Date: Tue, 16 Mar 2021 20:48:40 +0100 Subject: [PATCH 531/654] fix(JS): Use default form.action for django3.1 (where this attribute is absent) --- feincms/static/feincms/item_editor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feincms/static/feincms/item_editor.js b/feincms/static/feincms/item_editor.js index a4d25f93b..2a2c4de7f 100644 --- a/feincms/static/feincms/item_editor.js +++ b/feincms/static/feincms/item_editor.js @@ -496,7 +496,8 @@ if (!Array.prototype.indexOf) { $('form').submit(function(){ give_ordering_to_content_types(); var form = $(this); - form.attr('action', form.attr('action')+window.location.hash); + var action = form.attr("action") || ""; + form.attr('action', action + window.location.hash); return true; }); From 576b252bff6fdc808be9a0eb70768c72febf42ac Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 22 Mar 2021 20:19:16 +0100 Subject: [PATCH 532/654] Format the code to fix line length errors --- feincms/content/filer/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py index e2eaf1ea4..2feba4578 100644 --- a/feincms/content/filer/models.py +++ b/feincms/content/filer/models.py @@ -50,7 +50,9 @@ def render(self, **kwargs): ) class FilerFileContent(ContentWithFilerFile): - mediafile = FilerFileField(verbose_name=_("file"), related_name="+", on_delete=models.CASCADE) + mediafile = FilerFileField( + verbose_name=_("file"), related_name="+", on_delete=models.CASCADE + ) file_type = "file" type = "download" @@ -86,7 +88,9 @@ class FilerImageContent(ContentWithFilerFile): must_always_publish_copyright, date_taken, file, id, is_public, url """ - mediafile = FilerImageField(verbose_name=_("image"), related_name="+", on_delete=models.CASCADE) + mediafile = FilerImageField( + verbose_name=_("image"), related_name="+", on_delete=models.CASCADE + ) caption = models.CharField(_("caption"), max_length=1000, blank=True) url = models.CharField(_("URL"), max_length=1000, blank=True) From 31712d8e54a9af46b655ae51701771bc8dcfb5c1 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 22 Mar 2021 20:19:50 +0100 Subject: [PATCH 533/654] No need to run flake8 checks inside each job --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f09bcd3ad..4e3cab945 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,6 +59,6 @@ matrix: - env: REQ="https://github.com/django/django/archive/master.zip django-mptt" install: - pip install -U pip wheel setuptools - - pip install $REQ Pillow flake8 pytz six + - pip install $REQ Pillow pytz six - python setup.py install -script: "cd tests && ./manage.py test testapp && cd .. && flake8 ." +script: "cd tests && ./manage.py test testapp" From 8f7cb92c4118e9bead092b35200c338fb2926f3c Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Mon, 22 Mar 2021 20:56:46 +0100 Subject: [PATCH 534/654] Reformat everything, add GitHub action workflows for testing --- .editorconfig | 12 + .eslintrc.js | 19 + .github/workflows/tests.yml | 50 + .gitignore | 1 + .prettierignore | 3 + CHANGELOG.rst | 6 + README.rst | 6 +- docs/conf.py | 99 +- feincms/__init__.py | 7 +- feincms/_internal.py | 1 + feincms/admin/__init__.py | 3 +- feincms/admin/filters.py | 2 +- feincms/admin/tree_editor.py | 10 +- feincms/content/application/models.py | 9 +- feincms/content/contactform/__init__.py | 1 + feincms/content/filer/models.py | 3 +- feincms/content/medialibrary/models.py | 1 + feincms/contrib/fields.py | 7 +- feincms/contrib/tagging.py | 5 +- feincms/default_settings.py | 1 + feincms/extensions/__init__.py | 3 +- feincms/extensions/base.py | 4 +- feincms/extensions/changedate.py | 2 +- feincms/extensions/datepublisher.py | 2 +- feincms/extensions/translations.py | 2 +- .../commands/medialibrary_to_filer.py | 5 +- feincms/management/commands/rebuild_mptt.py | 1 + feincms/models.py | 8 +- feincms/module/extensions/changedate.py | 1 + feincms/module/extensions/ct_tracker.py | 1 + feincms/module/extensions/datepublisher.py | 1 + feincms/module/extensions/featured.py | 1 + feincms/module/extensions/seo.py | 1 + feincms/module/extensions/translations.py | 1 + feincms/module/medialibrary/__init__.py | 1 + feincms/module/medialibrary/admin.py | 3 +- feincms/module/medialibrary/fields.py | 5 +- feincms/module/medialibrary/forms.py | 2 +- feincms/module/medialibrary/modeladmins.py | 8 +- feincms/module/medialibrary/models.py | 4 +- feincms/module/medialibrary/zip.py | 2 +- feincms/module/page/admin.py | 4 +- feincms/module/page/extensions/navigation.py | 6 +- feincms/module/page/forms.py | 3 +- feincms/module/page/modeladmins.py | 9 +- feincms/module/page/models.py | 4 +- feincms/module/page/sitemap.py | 2 +- feincms/signals.py | 1 + feincms/static/feincms/item_editor.css | 304 +++-- feincms/static/feincms/item_editor.js | 1160 +++++++++-------- feincms/static/feincms/tree_editor.css | 33 +- feincms/static/feincms/tree_editor.js | 765 ++++++----- .../templatetags/applicationcontent_tags.py | 1 + feincms/templatetags/feincms_thumbnail.py | 10 +- feincms/translations.py | 1 - feincms/urls.py | 12 +- feincms/utils/__init__.py | 3 +- feincms/views/decorators.py | 1 + package.json | 16 + setup.cfg | 63 +- setup.py | 60 +- tests/manage.py | 1 + tests/testapp/applicationcontent_urls.py | 28 +- .../migrate/medialibrary/0001_initial.py | 3 +- tests/testapp/migrate/page/0001_initial.py | 3 +- tests/testapp/migrations/0001_initial.py | 3 +- tests/testapp/models.py | 7 +- tests/testapp/settings.py | 4 +- tests/testapp/tests/__init__.py | 3 +- tests/testapp/tests/test_cms.py | 3 +- tests/testapp/tests/test_extensions.py | 10 +- tests/testapp/tests/test_page.py | 6 +- tests/testapp/tests/test_stuff.py | 5 +- tests/testapp/urls.py | 16 +- tox.ini | 62 +- yarn.lock | 762 +++++++++++ 76 files changed, 2385 insertions(+), 1293 deletions(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.js create mode 100644 .github/workflows/tests.yml create mode 100644 .prettierignore create mode 100644 package.json create mode 100644 yarn.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..fd4bd13c7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.py] +indent_size = 4 diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..8e3d952be --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,19 @@ +module.exports = { + env: { + browser: true, + es6: true, + }, + extends: "eslint:recommended", + parserOptions: { + ecmaVersion: 2018, + }, + plugins: ["prettier"], + rules: { + indent: ["error", 2], + "linebreak-style": ["error", "unix"], + semi: ["error", "always"], + "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + "prettier/prettier": "error", + quotes: 0, + }, +}; diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..4ef9a6c36 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,50 @@ +name: Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + tests: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: + - 2.7 + - 3.6 + - 3.7 + - 3.8 + - 3.9 + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip wheel setuptools tox + - name: Run tox targets for ${{ matrix.python-version }} + run: | + ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") + TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') python -m tox + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip tox + - name: Run lint + run: tox -e style diff --git a/.gitignore b/.gitignore index 5dd000736..4cdbee3c0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ venv .coverage htmlcov test.zip +node_modules diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..1c0ca47e9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +feincms/static/feincms/jquery-1.11.3.min.js +feincms/static/feincms/jquery-ui-1.10.3.custom.min.js +feincms/static/feincms/js.cookie.js diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b327491c3..4f12ac9b8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,12 @@ Change log `Next version`_ ~~~~~~~~~~~~~~~ +- Renamed the main branch to main. +- Switched to a declarative setup. +- Switched to GitHub actions. +- Sorted imports. +- Reformated the JavaScript code using prettier. + `v1.19.0`_ (2021-03-04) ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index a3fb80618..cf9cf1901 100644 --- a/README.rst +++ b/README.rst @@ -2,10 +2,8 @@ FeinCMS - An extensible Django-based CMS ======================================== -.. image:: https://travis-ci.org/feincms/feincms.svg?branch=next - :target: https://travis-ci.org/feincms/feincms -.. image:: https://travis-ci.org/feincms/feincms.svg?branch=master - :target: https://travis-ci.org/feincms/feincms +.. image:: https://github.com/feincms/feincms/workflows/Tests/badge.svg + :target: https://github.com/feincms/feincms When was the last time, that a pre-built software package you wanted to use got many things right, but in the end, you still needed to modify diff --git a/docs/conf.py b/docs/conf.py index 488cb87be..ff00bfac2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,6 +14,7 @@ import os import sys + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -25,20 +26,20 @@ extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8' +# source_encoding = 'utf-8' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'FeinCMS' -copyright = u'2009-2010, Feinheit GmbH and contributors' +project = u"FeinCMS" +copyright = u"2009-2010, Feinheit GmbH and contributors" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -48,46 +49,47 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) import feincms -version = '.'.join(map(str, feincms.VERSION)) + +version = ".".join(map(str, feincms.VERSION)) # The full version, including alpha/beta/rc tags. release = feincms.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. -#unused_docs = [] +# unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. -exclude_trees = ['_build'] +exclude_trees = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- @@ -95,104 +97,109 @@ # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. # html_theme_path = ['_theme'] -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_use_modindex = True +# html_use_modindex = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' +# html_file_suffix = '' # Output file base name for HTML help builder. -htmlhelp_basename = 'FeinCMSdoc' +htmlhelp_basename = "FeinCMSdoc" # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -latex_paper_size = 'a4' +latex_paper_size = "a4" # The font size ('10pt', '11pt' or '12pt'). -latex_font_size = '10pt' +latex_font_size = "10pt" # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [( - 'index', 'FeinCMS.tex', u'FeinCMS Documentation', - u'Feinheit GmbH and contributors', 'manual'), +latex_documents = [ + ( + "index", + "FeinCMS.tex", + u"FeinCMS Documentation", + u"Feinheit GmbH and contributors", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # Additional stuff for the LaTeX preamble. -#latex_preamble = '' +# latex_preamble = '' # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_use_modindex = True +# latex_use_modindex = True diff --git a/feincms/__init__.py b/feincms/__init__.py index 392b5aed7..1ec248461 100644 --- a/feincms/__init__.py +++ b/feincms/__init__.py @@ -1,14 +1,16 @@ from __future__ import absolute_import, unicode_literals + VERSION = (1, 19, 0) __version__ = ".".join(map(str, VERSION)) class LazySettings(object): def _load_settings(self): - from feincms import default_settings from django.conf import settings as django_settings + from feincms import default_settings + for key in dir(default_settings): if not key.startswith("FEINCMS_"): continue @@ -59,9 +61,10 @@ def ensure_completely_loaded(force=False): # Here we flush the caches rather than actually _filling them so # that relations defined after all content types registrations # don't miss out. - import django from distutils.version import LooseVersion + import django + if LooseVersion(django.get_version()) < LooseVersion("1.8"): for model in apps.get_models(): diff --git a/feincms/_internal.py b/feincms/_internal.py index 44a669748..f0034dfee 100644 --- a/feincms/_internal.py +++ b/feincms/_internal.py @@ -7,6 +7,7 @@ from __future__ import absolute_import, unicode_literals from distutils.version import LooseVersion + from django import get_version from django.template.loader import render_to_string diff --git a/feincms/admin/__init__.py b/feincms/admin/__init__.py index 9008a9982..9bd00dfcb 100644 --- a/feincms/admin/__init__.py +++ b/feincms/admin/__init__.py @@ -1,7 +1,8 @@ from __future__ import absolute_import from django.contrib.admin.filters import FieldListFilter -from .filters import ParentFieldListFilter, CategoryFieldListFilter + +from .filters import CategoryFieldListFilter, ParentFieldListFilter FieldListFilter.register( diff --git a/feincms/admin/filters.py b/feincms/admin/filters.py index 2d8bcbf0c..bb269dd05 100644 --- a/feincms/admin/filters.py +++ b/feincms/admin/filters.py @@ -6,12 +6,12 @@ from __future__ import absolute_import, unicode_literals +from django import VERSION as DJANGO_VERSION from django.contrib.admin.filters import ChoicesFieldListFilter from django.db.models import Count from django.utils.encoding import smart_text from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ -from django import VERSION as DJANGO_VERSION from feincms.utils import shorten_string diff --git a/feincms/admin/tree_editor.py b/feincms/admin/tree_editor.py index 6c22aa627..01ba0f2c6 100644 --- a/feincms/admin/tree_editor.py +++ b/feincms/admin/tree_editor.py @@ -4,12 +4,12 @@ from __future__ import absolute_import, unicode_literals -from functools import reduce import json import logging +from functools import reduce -from django.contrib.admin.views import main from django.contrib.admin.actions import delete_selected +from django.contrib.admin.views import main from django.contrib.auth import get_permission_codename from django.db.models import Q from django.http import ( @@ -19,17 +19,17 @@ HttpResponseNotFound, HttpResponseServerError, ) +from django.utils.encoding import force_text from django.utils.html import escape from django.utils.safestring import mark_safe -from django.utils.translation import gettext_lazy as _, gettext -from django.utils.encoding import force_text - +from django.utils.translation import gettext, gettext_lazy as _ from mptt.exceptions import InvalidMove from mptt.forms import MPTTAdminForm from feincms import settings from feincms.extensions import ExtensionModelAdmin + try: # Django<3 from django.contrib.staticfiles.templatetags.staticfiles import static diff --git a/feincms/content/application/models.py b/feincms/content/application/models.py index 9a5568b55..6eb4735db 100644 --- a/feincms/content/application/models.py +++ b/feincms/content/application/models.py @@ -1,10 +1,10 @@ from __future__ import absolute_import +import warnings from collections import OrderedDict from email.utils import parsedate from functools import partial, wraps from time import mktime -import warnings from django.conf import settings from django.core.cache import cache @@ -16,14 +16,15 @@ from django.utils.safestring import mark_safe from django.utils.translation import get_language, gettext_lazy as _ + try: from django.urls import ( NoReverseMatch, - reverse, - get_script_prefix, - set_script_prefix, Resolver404, + get_script_prefix, resolve, + reverse, + set_script_prefix, ) except ImportError: from django.core.urlresolvers import ( diff --git a/feincms/content/contactform/__init__.py b/feincms/content/contactform/__init__.py index c25396137..5d3fb54d8 100644 --- a/feincms/content/contactform/__init__.py +++ b/feincms/content/contactform/__init__.py @@ -3,6 +3,7 @@ import warnings + warnings.warn( "The contactform content has been deprecated. Use form-designer instead.", DeprecationWarning, diff --git a/feincms/content/filer/models.py b/feincms/content/filer/models.py index 2feba4578..5213ead70 100644 --- a/feincms/content/filer/models.py +++ b/feincms/content/filer/models.py @@ -5,8 +5,9 @@ from django.db import models from django.utils.translation import gettext_lazy as _ -from feincms.admin.item_editor import FeinCMSInline from feincms._internal import ct_render_to_string +from feincms.admin.item_editor import FeinCMSInline + try: from filer.fields.file import FilerFileField diff --git a/feincms/content/medialibrary/models.py b/feincms/content/medialibrary/models.py index bc7d2f474..0e0a10ed3 100644 --- a/feincms/content/medialibrary/models.py +++ b/feincms/content/medialibrary/models.py @@ -5,6 +5,7 @@ from feincms.module.medialibrary.contents import MediaFileContent + warnings.warn( "Import MediaFileContent from feincms.module.medialibrary.contents.", DeprecationWarning, diff --git a/feincms/contrib/fields.py b/feincms/contrib/fields.py index 6d359e5dd..4a03fe972 100644 --- a/feincms/contrib/fields.py +++ b/feincms/contrib/fields.py @@ -2,13 +2,12 @@ import json import logging -import six from distutils.version import LooseVersion -from django import get_version -from django import forms -from django.db import models +import six +from django import forms, get_version from django.core.serializers.json import DjangoJSONEncoder +from django.db import models class JSONFormField(forms.fields.CharField): diff --git a/feincms/contrib/tagging.py b/feincms/contrib/tagging.py index f5652ea03..2b496a678 100644 --- a/feincms/contrib/tagging.py +++ b/feincms/contrib/tagging.py @@ -11,16 +11,15 @@ from __future__ import absolute_import, unicode_literals import six - -from django import forms, VERSION +from django import VERSION, forms from django.contrib.admin.widgets import FilteredSelectMultiple from django.db.models.signals import pre_save from django.utils.translation import gettext_lazy as _ - from tagging.fields import TagField from tagging.models import Tag from tagging.utils import parse_tag_input + try: from tagging.registry import AlreadyRegistered except ImportError: diff --git a/feincms/default_settings.py b/feincms/default_settings.py index a0d20be5c..93cbc88be 100644 --- a/feincms/default_settings.py +++ b/feincms/default_settings.py @@ -12,6 +12,7 @@ from django.conf import settings + # e.g. 'uploads' if you would prefer /uploads/imagecontent/test.jpg # to /imagecontent/test.jpg. FEINCMS_UPLOAD_PREFIX = getattr(settings, "FEINCMS_UPLOAD_PREFIX", "") diff --git a/feincms/extensions/__init__.py b/feincms/extensions/__init__.py index ad37b177f..74a10a8a6 100644 --- a/feincms/extensions/__init__.py +++ b/feincms/extensions/__init__.py @@ -1,12 +1,13 @@ from __future__ import absolute_import from .base import ( - ExtensionsMixin, Extension, ExtensionModelAdmin, + ExtensionsMixin, prefetch_modeladmin_get_queryset, ) + __all__ = ( "ExtensionsMixin", "Extension", diff --git a/feincms/extensions/base.py b/feincms/extensions/base.py index e2701090a..375bf045b 100644 --- a/feincms/extensions/base.py +++ b/feincms/extensions/base.py @@ -4,10 +4,10 @@ from __future__ import absolute_import, unicode_literals -from functools import wraps -import six import inspect +from functools import wraps +import six from django.contrib import admin from django.core.exceptions import ImproperlyConfigured diff --git a/feincms/extensions/changedate.py b/feincms/extensions/changedate.py index 880641fba..cf07cbb58 100644 --- a/feincms/extensions/changedate.py +++ b/feincms/extensions/changedate.py @@ -7,7 +7,7 @@ from __future__ import absolute_import, unicode_literals -from email.utils import parsedate_tz, mktime_tz +from email.utils import mktime_tz, parsedate_tz from time import mktime from django.db import models diff --git a/feincms/extensions/datepublisher.py b/feincms/extensions/datepublisher.py index 695d09585..14f5dda93 100644 --- a/feincms/extensions/datepublisher.py +++ b/feincms/extensions/datepublisher.py @@ -11,7 +11,6 @@ from __future__ import absolute_import, unicode_literals from datetime import datetime -from pytz.exceptions import AmbiguousTimeError from django.db import models from django.db.models import Q @@ -19,6 +18,7 @@ from django.utils.cache import patch_response_headers from django.utils.html import mark_safe from django.utils.translation import gettext_lazy as _ +from pytz.exceptions import AmbiguousTimeError from feincms import extensions diff --git a/feincms/extensions/translations.py b/feincms/extensions/translations.py index 3893808a2..662065017 100644 --- a/feincms/extensions/translations.py +++ b/feincms/extensions/translations.py @@ -28,8 +28,8 @@ from django.utils.translation import gettext_lazy as _ from feincms import extensions, settings -from feincms.translations import is_primary_language from feincms._internal import monkeypatch_method, monkeypatch_property +from feincms.translations import is_primary_language # ------------------------------------------------------------------------ diff --git a/feincms/management/commands/medialibrary_to_filer.py b/feincms/management/commands/medialibrary_to_filer.py index 2ac3fefec..cf1f08688 100644 --- a/feincms/management/commands/medialibrary_to_filer.py +++ b/feincms/management/commands/medialibrary_to_filer.py @@ -1,16 +1,15 @@ from __future__ import absolute_import, unicode_literals +from django.contrib.auth.models import User from django.core.files import File as DjangoFile from django.core.management.base import NoArgsCommand -from django.contrib.auth.models import User +from filer.models import File, Image from feincms.contents import FilerFileContent, FilerImageContent from feincms.module.medialibrary.contents import MediaFileContent from feincms.module.medialibrary.models import MediaFile from feincms.module.page.models import Page -from filer.models import File, Image - PageMediaFileContent = Page.content_type_for(MediaFileContent) PageFilerFileContent = Page.content_type_for(FilerFileContent) diff --git a/feincms/management/commands/rebuild_mptt.py b/feincms/management/commands/rebuild_mptt.py index f290daa50..6a77ab3c1 100644 --- a/feincms/management/commands/rebuild_mptt.py +++ b/feincms/management/commands/rebuild_mptt.py @@ -10,6 +10,7 @@ from __future__ import absolute_import, unicode_literals + try: from django.core.management.base import NoArgsCommand as BaseCommand except ImportError: diff --git a/feincms/models.py b/feincms/models.py index 461c31df1..5b93fd4e6 100644 --- a/feincms/models.py +++ b/feincms/models.py @@ -7,13 +7,13 @@ from __future__ import absolute_import, unicode_literals -from collections import OrderedDict -from functools import reduce -import six -import sys import operator +import sys import warnings +from collections import OrderedDict +from functools import reduce +import six from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured from django.db import connections, models diff --git a/feincms/module/extensions/changedate.py b/feincms/module/extensions/changedate.py index fdfd93475..865cdb062 100644 --- a/feincms/module/extensions/changedate.py +++ b/feincms/module/extensions/changedate.py @@ -5,6 +5,7 @@ from feincms.extensions.changedate import * + warnings.warn( "Import %(name)s from feincms.extensions.%(name)s" % {"name": __name__.split(".")[-1]}, diff --git a/feincms/module/extensions/ct_tracker.py b/feincms/module/extensions/ct_tracker.py index f810b346e..6ea11bd32 100644 --- a/feincms/module/extensions/ct_tracker.py +++ b/feincms/module/extensions/ct_tracker.py @@ -5,6 +5,7 @@ from feincms.extensions.ct_tracker import * + warnings.warn( "Import %(name)s from feincms.extensions.%(name)s" % {"name": __name__.split(".")[-1]}, diff --git a/feincms/module/extensions/datepublisher.py b/feincms/module/extensions/datepublisher.py index 313568d74..b95b01633 100644 --- a/feincms/module/extensions/datepublisher.py +++ b/feincms/module/extensions/datepublisher.py @@ -5,6 +5,7 @@ from feincms.extensions.datepublisher import * + warnings.warn( "Import %(name)s from feincms.extensions.%(name)s" % {"name": __name__.split(".")[-1]}, diff --git a/feincms/module/extensions/featured.py b/feincms/module/extensions/featured.py index 7713d026b..5b947c84f 100644 --- a/feincms/module/extensions/featured.py +++ b/feincms/module/extensions/featured.py @@ -5,6 +5,7 @@ from feincms.extensions.featured import * + warnings.warn( "Import %(name)s from feincms.extensions.%(name)s" % {"name": __name__.split(".")[-1]}, diff --git a/feincms/module/extensions/seo.py b/feincms/module/extensions/seo.py index aa386950f..d81fe2b69 100644 --- a/feincms/module/extensions/seo.py +++ b/feincms/module/extensions/seo.py @@ -5,6 +5,7 @@ from feincms.extensions.seo import * + warnings.warn( "Import %(name)s from feincms.extensions.%(name)s" % {"name": __name__.split(".")[-1]}, diff --git a/feincms/module/extensions/translations.py b/feincms/module/extensions/translations.py index c89c8d538..68e27512a 100644 --- a/feincms/module/extensions/translations.py +++ b/feincms/module/extensions/translations.py @@ -5,6 +5,7 @@ from feincms.extensions.translations import * + warnings.warn( "Import %(name)s from feincms.extensions.%(name)s" % {"name": __name__.split(".")[-1]}, diff --git a/feincms/module/medialibrary/__init__.py b/feincms/module/medialibrary/__init__.py index 98a59597d..b2d495235 100644 --- a/feincms/module/medialibrary/__init__.py +++ b/feincms/module/medialibrary/__init__.py @@ -6,6 +6,7 @@ import logging + # ------------------------------------------------------------------------ logger = logging.getLogger("feincms.medialibrary") diff --git a/feincms/module/medialibrary/admin.py b/feincms/module/medialibrary/admin.py index 922450af9..91103eb38 100644 --- a/feincms/module/medialibrary/admin.py +++ b/feincms/module/medialibrary/admin.py @@ -6,8 +6,9 @@ from django.contrib import admin -from .models import Category, MediaFile from .modeladmins import CategoryAdmin, MediaFileAdmin +from .models import Category, MediaFile + # ------------------------------------------------------------------------ admin.site.register(Category, CategoryAdmin) diff --git a/feincms/module/medialibrary/fields.py b/feincms/module/medialibrary/fields.py index 9682bd6b9..3d7417efa 100644 --- a/feincms/module/medialibrary/fields.py +++ b/feincms/module/medialibrary/fields.py @@ -5,9 +5,7 @@ from __future__ import absolute_import, unicode_literals import six - -from django.contrib.admin.widgets import AdminFileWidget -from django.contrib.admin.widgets import ForeignKeyRawIdWidget +from django.contrib.admin.widgets import AdminFileWidget, ForeignKeyRawIdWidget from django.db import models from django.utils.html import escape from django.utils.safestring import mark_safe @@ -15,6 +13,7 @@ from feincms.admin.item_editor import FeinCMSInline from feincms.utils import shorten_string + from .models import MediaFile from .thumbnail import admin_thumbnail diff --git a/feincms/module/medialibrary/forms.py b/feincms/module/medialibrary/forms.py index 38df3ab8f..e48f73539 100644 --- a/feincms/module/medialibrary/forms.py +++ b/feincms/module/medialibrary/forms.py @@ -12,8 +12,8 @@ from feincms import settings from . import logger -from .models import Category, MediaFile from .fields import AdminFileWithPreviewWidget +from .models import Category, MediaFile # ------------------------------------------------------------------------ diff --git a/feincms/module/medialibrary/modeladmins.py b/feincms/module/medialibrary/modeladmins.py index adc217430..7cc72e36c 100644 --- a/feincms/module/medialibrary/modeladmins.py +++ b/feincms/module/medialibrary/modeladmins.py @@ -8,8 +8,7 @@ from django import forms from django.conf import settings as django_settings -from django.contrib import admin -from django.contrib import messages +from django.contrib import admin, messages from django.contrib.auth.decorators import permission_required from django.contrib.sites.shortcuts import get_current_site from django.core.files.images import get_image_dimensions @@ -17,9 +16,10 @@ from django.shortcuts import render from django.template.defaultfilters import filesizeformat from django.utils.safestring import mark_safe -from django.utils.translation import ungettext, gettext_lazy as _ +from django.utils.translation import gettext_lazy as _, ungettext from django.views.decorators.csrf import csrf_protect + try: from django.urls import reverse except ImportError: @@ -29,8 +29,8 @@ from feincms.translations import admin_translationinline, lookup_translations from feincms.utils import shorten_string -from .models import Category, MediaFileTranslation from .forms import MediaCategoryAdminForm, MediaFileAdminForm +from .models import Category, MediaFileTranslation from .thumbnail import admin_thumbnail from .zip import import_zipfile diff --git a/feincms/module/medialibrary/models.py b/feincms/module/medialibrary/models.py index 738beef14..beca82997 100644 --- a/feincms/module/medialibrary/models.py +++ b/feincms/module/medialibrary/models.py @@ -6,9 +6,9 @@ import os import re -import six import django +import six from django.db import models from django.db.models.signals import post_delete from django.dispatch.dispatcher import receiver @@ -19,9 +19,9 @@ from feincms import settings from feincms.models import ExtensionsMixin from feincms.translations import ( + TranslatedObjectManager, TranslatedObjectMixin, Translation, - TranslatedObjectManager, ) from . import logger diff --git a/feincms/module/medialibrary/zip.py b/feincms/module/medialibrary/zip.py index 2e91376f6..3f16b7641 100644 --- a/feincms/module/medialibrary/zip.py +++ b/feincms/module/medialibrary/zip.py @@ -10,9 +10,9 @@ from __future__ import absolute_import, unicode_literals import json -import zipfile import os import time +import zipfile from django.conf import settings as django_settings from django.core.files.base import ContentFile diff --git a/feincms/module/page/admin.py b/feincms/module/page/admin.py index e4c61602e..8a396aa88 100644 --- a/feincms/module/page/admin.py +++ b/feincms/module/page/admin.py @@ -8,8 +8,10 @@ from django.core.exceptions import ImproperlyConfigured from feincms import ensure_completely_loaded, settings -from .models import Page + from .modeladmins import PageAdmin +from .models import Page + try: from django.core.exceptions import FieldDoesNotExist diff --git a/feincms/module/page/extensions/navigation.py b/feincms/module/page/extensions/navigation.py index 9f03311a0..85bab288e 100644 --- a/feincms/module/page/extensions/navigation.py +++ b/feincms/module/page/extensions/navigation.py @@ -9,17 +9,17 @@ from __future__ import absolute_import, unicode_literals -from collections import OrderedDict -import six import types +from collections import OrderedDict +import six from django.db import models from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from feincms import extensions -from feincms.utils import get_object, shorten_string from feincms._internal import monkeypatch_method +from feincms.utils import get_object, shorten_string class TypeRegistryMetaClass(type): diff --git a/feincms/module/page/forms.py b/feincms/module/page/forms.py index 114a97378..c86c4841b 100644 --- a/feincms/module/page/forms.py +++ b/feincms/module/page/forms.py @@ -11,11 +11,10 @@ from django.forms.models import model_to_dict from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ +from mptt.forms import MPTTAdminForm from feincms import ensure_completely_loaded -from mptt.forms import MPTTAdminForm - class RedirectToWidget(ForeignKeyRawIdWidget): def label_for_value(self, value): diff --git a/feincms/module/page/modeladmins.py b/feincms/module/page/modeladmins.py index 5f0b94fbe..070c2a735 100644 --- a/feincms/module/page/modeladmins.py +++ b/feincms/module/page/modeladmins.py @@ -8,13 +8,13 @@ from threading import local from django.conf import settings as django_settings -from django.core.exceptions import PermissionDenied +from django.contrib import admin, messages from django.contrib.contenttypes.models import ContentType -from django.contrib import admin -from django.contrib import messages +from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect from django.utils.translation import gettext_lazy as _ + try: from django.urls import reverse except ImportError: @@ -26,8 +26,7 @@ # Newer Django versions. from django.templatetags.static import static -from feincms import ensure_completely_loaded -from feincms import settings +from feincms import ensure_completely_loaded, settings from feincms.admin import item_editor, tree_editor # ------------------------------------------------------------------------ diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py index 281d7364b..e642ce7f2 100644 --- a/feincms/module/page/models.py +++ b/feincms/module/page/models.py @@ -5,13 +5,13 @@ from __future__ import absolute_import, unicode_literals import six - from django.core.exceptions import PermissionDenied from django.db import models from django.db.models import Q from django.http import Http404 from django.utils.translation import gettext_lazy as _ + try: from django.urls import reverse except ImportError: @@ -23,8 +23,8 @@ from feincms.models import create_base_model from feincms.module.mixins import ContentModelMixin from feincms.module.page import processors +from feincms.utils import get_model_instance, match_model_string, shorten_string from feincms.utils.managers import ActiveAwareContentManagerMixin -from feincms.utils import shorten_string, match_model_string, get_model_instance # ------------------------------------------------------------------------ diff --git a/feincms/module/page/sitemap.py b/feincms/module/page/sitemap.py index c5cbd5850..46667b7b5 100644 --- a/feincms/module/page/sitemap.py +++ b/feincms/module/page/sitemap.py @@ -5,8 +5,8 @@ from __future__ import absolute_import, unicode_literals from django.apps import apps -from django.db.models import Max from django.contrib.sitemaps import Sitemap +from django.db.models import Max from feincms import settings diff --git a/feincms/signals.py b/feincms/signals.py index 2d4fca135..0655b3ec4 100644 --- a/feincms/signals.py +++ b/feincms/signals.py @@ -11,6 +11,7 @@ from django.dispatch import Signal + # ------------------------------------------------------------------------ # This signal is sent when an item editor managed object is completely # saved, especially including all foreign or manytomany dependencies. diff --git a/feincms/static/feincms/item_editor.css b/feincms/static/feincms/item_editor.css index 69ba18640..3f80d1539 100644 --- a/feincms/static/feincms/item_editor.css +++ b/feincms/static/feincms/item_editor.css @@ -1,278 +1,312 @@ .navi_tab { - float:left; - padding: 8px 10px; - cursor:pointer; - margin-top:3px; - font-weight: bold; - font-size: 11px; - color: #666; - background: #f6f6f6; - border: 1px solid #eee; - text-transform: uppercase; + float: left; + padding: 8px 10px; + cursor: pointer; + margin-top: 3px; + font-weight: bold; + font-size: 11px; + color: #666; + background: #f6f6f6; + border: 1px solid #eee; + text-transform: uppercase; } .tab_active { - background: #79aec8; - color: white; - border-color: #79aec8; + background: #79aec8; + color: white; + border-color: #79aec8; } #feincmsmain { - clear:both; - padding: 10px 10px 10px 10px; - border: 1px solid #eee; - margin: 0 0 10px 0; + clear: both; + padding: 10px 10px 10px 10px; + border: 1px solid #eee; + margin: 0 0 10px 0; } .panel { - display:none; - position:relative; - padding-bottom: 39px; + display: none; + position: relative; + padding-bottom: 39px; } .order-item { - margin: 0 0 10px 0; - position:relative; - + margin: 0 0 10px 0; + position: relative; } .order-item h2 { - background-image: url('img/arrow-move.png'); - background-repeat: no-repeat; - background-position: 6px 9px; + background-image: url("img/arrow-move.png"); + background-repeat: no-repeat; + background-position: 6px 9px; } .order-item .handle { - display: inline-block; - height: 14px; - width: 15px; - cursor: move; + display: inline-block; + height: 14px; + width: 15px; + cursor: move; } .order-item .collapse { - cursor: pointer; - /*color: #444;*/ - font-weight: normal; + cursor: pointer; + /*color: #444;*/ + font-weight: normal; - border-bottom: 1px solid rgba(255, 255, 255, 0.25); + border-bottom: 1px solid rgba(255, 255, 255, 0.25); } .order-item .collapse:hover { - opacity: 0.7; + opacity: 0.7; } .item-delete { - cursor:pointer; - float:right; - margin: 4px 3px 0px 0; + cursor: pointer; + float: right; + margin: 4px 3px 0px 0; } -.highlight, .helper { - height: 34px; - margin: 0 0 10px 0; - border: none; - opacity: 0.3; - background: #79aec8; +.highlight, +.helper { + height: 34px; + margin: 0 0 10px 0; + border: none; + opacity: 0.3; + background: #79aec8; } -.helper{ - height: 25px !important; - opacity: 1; +.helper { + height: 25px !important; + opacity: 1; } .button { - margin:5px; padding:5px; - font-weight: bold; - cursor:pointer; - border: 1px solid #678; + margin: 5px; + padding: 5px; + font-weight: bold; + cursor: pointer; + border: 1px solid #678; } select { - max-width: 580px; + max-width: 580px; } #feincmsmain_wrapper { - margin: 10px 0 30px 0; + margin: 10px 0 30px 0; } -.clearfix { *zoom:1; } -.clearfix:before, .clearfix:after { content: " "; display: table; } -.clearfix:after { clear: both; } +.clearfix { + *zoom: 1; +} +.clearfix:before, +.clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} textarea { - width: 580px; - margin-top:5px; - margin-bottom:5px; + width: 580px; + margin-top: 5px; + margin-bottom: 5px; } .inline-group .tabular textarea { - width: auto; + width: auto; } .item-controls { - position: absolute; - top: 2px; - right: 32px; + position: absolute; + top: 2px; + right: 32px; } .item-control-unit { - float:left; + float: left; } .item-controls select { - margin-left: 7px; + margin-left: 7px; } .machine-control { - padding: 5px 10px 5px 10px; - border: 1px solid #ccc; - background-color: #edf3fe; - position:absolute; - left:-11px; - bottom:-30px; - width: 100%; + padding: 5px 10px 5px 10px; + border: 1px solid #ccc; + background-color: #edf3fe; + position: absolute; + left: -11px; + bottom: -30px; + width: 100%; - background: #f8f8f8; - border: 1px solid #eee; - height: 55px; + background: #f8f8f8; + border: 1px solid #eee; + height: 55px; } .machine-control .button { - padding-top: 6px; - padding-bottom: 6px; + padding-top: 6px; + padding-bottom: 6px; } .control-unit { - float:left; - padding: 0 20px 0 5px; - border-left: 1px solid #eee; + float: left; + padding: 0 20px 0 5px; + border-left: 1px solid #eee; } .control-unit:first-child { - border-left: none; + border-left: none; } .control-unit span { - font-weight:bold; + font-weight: bold; } a.actionbutton { - display: block; - background-repeat: no-repeat; - width:50px; - height:50px; - float:left; - margin: 5px 0 0 20px; - text-indent:-7000px; + display: block; + background-repeat: no-repeat; + width: 50px; + height: 50px; + float: left; + margin: 5px 0 0 20px; + text-indent: -7000px; } -a.richtextcontent { background: url(img/contenttypes.png) no-repeat 0 0; } -a.richtextcontent:hover { background-position: 0 -70px; } +a.richtextcontent { + background: url(img/contenttypes.png) no-repeat 0 0; +} +a.richtextcontent:hover { + background-position: 0 -70px; +} -a.imagecontent { background: url(img/contenttypes.png) no-repeat -70px 0; } -a.imagecontent:hover { background-position: -70px -70px; } +a.imagecontent { + background: url(img/contenttypes.png) no-repeat -70px 0; +} +a.imagecontent:hover { + background-position: -70px -70px; +} -a.gallerycontent { background: url(img/contenttypes.png) no-repeat -140px 0; } -a.gallerycontent:hover { background-position: -140px -70px; } +a.gallerycontent { + background: url(img/contenttypes.png) no-repeat -140px 0; +} +a.gallerycontent:hover { + background-position: -140px -70px; +} -a.oembedcontent { background: url(img/contenttypes.png) no-repeat -280px 0; } -a.oembedcontent:hover { background-position: -280px -70px; } +a.oembedcontent { + background: url(img/contenttypes.png) no-repeat -280px 0; +} +a.oembedcontent:hover { + background-position: -280px -70px; +} -a.pdfcontent { background: url(img/contenttypes.png) no-repeat -210px 0; } -a.pdfcontent:hover { background-position: -210px -70px; } +a.pdfcontent { + background: url(img/contenttypes.png) no-repeat -210px 0; +} +a.pdfcontent:hover { + background-position: -210px -70px; +} -a.audiocontent { background: url(img/contenttypes.png) no-repeat -350px 0; } -a.audiocontent:hover { background-position: -350px -70px; } +a.audiocontent { + background: url(img/contenttypes.png) no-repeat -350px 0; +} +a.audiocontent:hover { + background-position: -350px -70px; +} .control-unit select { - float: left; - position: relative; - top: 13px; + float: left; + position: relative; + top: 13px; } .empty-machine-msg { - margin:10px 0px 20px 20px; - font-size:14px; + margin: 10px 0px 20px 20px; + font-size: 14px; } td span select { - width:600px; + width: 600px; } - .change-template-button { - margin-left: 7em; - padding-left: 30px; + margin-left: 7em; + padding-left: 30px; } /* Allow nested lists in error items */ ul.errorlist ul { - margin-left: 1em; - padding-left: 0; - list-style-type: square; + margin-left: 1em; + padding-left: 0; + list-style-type: square; } ul.errorlist li li { - /* Avoid repeating the warning image every time*/ - background-image:none; - padding: 0; + /* Avoid repeating the warning image every time*/ + background-image: none; + padding: 0; } -div.order-machine div.inline-related > h3{ - display: none; +div.order-machine div.inline-related > h3 { + display: none; } .hidden-form-row { - display: none; + display: none; } #extension_options_wrapper { border-bottom: 1px solid #eee; } -#extension_options>.module.aligned { +#extension_options > .module.aligned { border-top: 1px solid #eee; margin-bottom: -1px; } - /* various overrides */ -#id_redirect_to { width: 20em; } /* raw_id_fields act-a-like for redirect_to */ +#id_redirect_to { + width: 20em; +} /* raw_id_fields act-a-like for redirect_to */ /* overwrite flat theme default label width because of problems with the CKEditor */ -.aligned .text label { width: auto; } - +.aligned .text label { + width: auto; +} /* django suit hacks */ /*********************/ #suit-center #feincmsmain { - clear:none; + clear: none; } #suit-center .panel { - padding-top: 15px; + padding-top: 15px; } #suit-center .form-horizontal .inline-related fieldset { - margin-top: 10px; + margin-top: 10px; } #suit-center .panel h2 { - color: white; - font-size: 13px; - line-height: 12px; - margin-left: 0; - text-shadow: none; + color: white; + font-size: 13px; + line-height: 12px; + margin-left: 0; + text-shadow: none; } #suit-center .item-delete { - margin: 3px 5px 0 0; + margin: 3px 5px 0 0; } #suit-center .order-item .handle { - height: 36px; + height: 36px; } #suit-center .order-machine .order-item { - margin-top: 10px; + margin-top: 10px; } diff --git a/feincms/static/feincms/item_editor.js b/feincms/static/feincms/item_editor.js index 2a2c4de7f..a64f9a57f 100644 --- a/feincms/static/feincms/item_editor.js +++ b/feincms/static/feincms/item_editor.js @@ -1,603 +1,687 @@ +/* global Downcoder, django, feincms */ +/* global IMG_DELETELINK_PATH, REGION_MAP, REGION_NAMES, ACTIVE_REGION, CONTENT_NAMES, FEINCMS_ITEM_EDITOR_GETTEXT, CONTENT_TYPE_BUTTONS */ +/* global contentblock_init_handlers, contentblock_move_handlers */ +/* global id_to_windowname */ +/* global template_regions */ + // IE<9 lacks Array.prototype.indexOf if (!Array.prototype.indexOf) { - Array.prototype.indexOf = function(needle) { - for (i=0, l=this.length; i").addClass("module aligned order-item item-wrapper-" + modvar); - var original_id_id = '#id_' + form.attr('id') + '-id'; - - var wrp = ['

']; - // If original has delete checkbox or this is a freshly added CT? Add delete link! - if($('.delete', form).length || !$(original_id_id, form).val()) { - wrp.push(''); - } - wrp.push(' '+modname+'

'); - wrp.push('
'); - fieldset.append(wrp.join("")); - - fieldset.children(".item-content").append(form); //relocates, not clone - - $("
").addClass("item-controls").appendTo(fieldset); - - return fieldset; - } - - - SELECTS = {}; - function save_content_type_selects() { - $('#feincmsmain>.panel').each(function() { - SELECTS[this.id.replace(/_body$/, '')] = $("select[name=order-machine-add-select]", this).clone().removeAttr("name"); - }); +(function ($) { + // Patch up urlify maps to generate nicer slugs in german + if (typeof Downcoder != "undefined") { + Downcoder.Initialize(); + Downcoder.map["ö"] = Downcoder.map["Ö"] = "oe"; + Downcoder.map["ä"] = Downcoder.map["Ä"] = "ae"; + Downcoder.map["ü"] = Downcoder.map["Ü"] = "ue"; + } + + function feincms_gettext(s) { + // Unfortunately, we cannot use Django's jsi18n view for this + // because it only sends translations from the package + // "django.conf" -- our own djangojs domain strings won't be + // picked up + + if (FEINCMS_ITEM_EDITOR_GETTEXT[s]) return FEINCMS_ITEM_EDITOR_GETTEXT[s]; + return s; + } + + function create_new_item_from_form(form, modname, modvar) { + let fieldset = $("
").addClass( + "module aligned order-item item-wrapper-" + modvar + ); + let original_id_id = "#id_" + form.attr("id") + "-id"; + + let wrp = ["

"]; + // If original has delete checkbox or this is a freshly added CT? Add delete link! + if ($(".delete", form).length || !$(original_id_id, form).val()) { + wrp.push(''); } - - function update_item_controls(item, target_region_id){ - var item_controls = item.find(".item-controls"); - item_controls.empty(); - - // Insert control unit - var insert_control = $("
").addClass("item-control-unit"); - var select_content = SELECTS[REGION_MAP[target_region_id]].clone(); - - select_content.change(function() { - var modvar = select_content.val(); - var modname = select_content.find("option:selected").html(); - var new_fieldset = create_new_fieldset_from_module(modvar, modname); - add_fieldset(target_region_id, new_fieldset, {where:'insertBefore', relative_to:item, animate:true}); - update_item_controls(new_fieldset, target_region_id); - - select_content.val(''); - }); - insert_control.append(select_content); - item_controls.append(insert_control); - - // Move control unit - if (REGION_MAP.length > 1) { - var wrp = []; - wrp.push('
'); - - var move_control = $(wrp.join("")); - move_control.find("select").change(function(){ - var move_to = $(this).val(); - move_item(REGION_MAP.indexOf(move_to), item); - }); - item_controls.append(move_control); // Add new one + wrp.push( + ' ' + + modname + + "

" + ); + wrp.push('
'); + fieldset.append(wrp.join("")); + + fieldset.children(".item-content").append(form); //relocates, not clone + + $("
").addClass("item-controls").appendTo(fieldset); + + return fieldset; + } + + const SELECTS = {}; + function save_content_type_selects() { + $("#feincmsmain>.panel").each(function () { + SELECTS[this.id.replace(/_body$/, "")] = $( + "select[name=order-machine-add-select]", + this + ) + .clone() + .removeAttr("name"); + }); + } + + function update_item_controls(item, target_region_id) { + let item_controls = item.find(".item-controls"); + item_controls.empty(); + + // Insert control unit + let insert_control = $("
").addClass("item-control-unit"); + let select_content = SELECTS[REGION_MAP[target_region_id]].clone(); + + select_content.change(function () { + let modvar = select_content.val(); + let modname = select_content.find("option:selected").html(); + let new_fieldset = create_new_fieldset_from_module(modvar, modname); + add_fieldset(target_region_id, new_fieldset, { + where: "insertBefore", + relative_to: item, + animate: true, + }); + update_item_controls(new_fieldset, target_region_id); + + select_content.val(""); + }); + insert_control.append(select_content); + item_controls.append(insert_control); + + // Move control unit + if (REGION_MAP.length > 1) { + let wrp = []; + wrp.push( + '
"); + + let move_control = $(wrp.join("")); + move_control.find("select").change(function () { + let move_to = $(this).val(); + move_item(REGION_MAP.indexOf(move_to), item); + }); + item_controls.append(move_control); // Add new one } + } + function create_new_fieldset_from_module(modvar, modname) { + let new_form = create_new_spare_form(modvar); + return create_new_item_from_form(new_form, modname, modvar); + } - function create_new_fieldset_from_module(modvar, modname) { - var new_form = create_new_spare_form(modvar); - return create_new_item_from_form(new_form, modname, modvar); - } - - function add_fieldset(region_id, item, how){ - /* `how` should be an object. + function add_fieldset(region_id, item, how) { + /* `how` should be an object. `how.where` should be one of: - 'append' -- last region - 'prepend' -- first region - 'insertBefore' -- insert before relative_to - 'insertAfter' -- insert after relative_to */ - // Default parameters - if (how) $.extend({ - where: 'append', - relative_to: undefined, - animate: false - }, how); - - item.hide(); - if(how.where == 'append' || how.where == 'prepend'){ - $("#"+ REGION_MAP[region_id] +"_body").children("div.order-machine")[how.where](item); - } - else if(how.where == 'insertBefore' || how.where == 'insertAfter'){ - if(how.relative_to){ - item[how.where](how.relative_to); - } - else{ - window.alert('DEBUG: invalid add_fieldset usage'); - return; - } - } - else{ - window.alert('DEBUG: invalid add_fieldset usage'); - return; - } - set_item_field_value(item, "region-choice-field", region_id); - init_contentblocks(); - - if (how.animate) { - item.fadeIn(800); - } - else { - item.show(); - } - } - - function create_new_spare_form(modvar) { - var old_form_count = parseInt($('#id_'+modvar+'_set-TOTAL_FORMS').val(), 10); - // **** UGLY CODE WARNING, avert your gaze! **** - // for some unknown reason, the add-button click handler function - // fails on the first triggerHandler call in some rare cases; - // we can detect this here and retry: - for(var i = 0; i < 2; i++){ - // Use Django's built-in inline spawing mechanism (Django 1.2+) - // must use django.jQuery since the bound function lives there: - django.jQuery('#'+modvar+'_set-group').find( - '.add-row a').triggerHandler('click'); - var new_form_count = parseInt($('#id_'+modvar+'_set-TOTAL_FORMS').val(), 10); - if(new_form_count > old_form_count){ - return $('#'+modvar+'_set-'+(new_form_count-1)); - } - } - } - - function set_item_field_value(item, field, value) { - // item: DOM object for the item's fieldset. - // field: "order-field" | "delete-field" | "region-choice-field" - if (field=="delete-field") - item.find("."+field).attr("checked",value); - else if (field=="region-choice-field") { - var old_region_id = REGION_MAP.indexOf(item.find("."+field).val()); - item.find("."+field).val(REGION_MAP[value]); - - // show/hide the empty machine message in the source and - // target region. - old_region_item = $("#"+REGION_MAP[old_region_id]+"_body"); - if (old_region_item.children("div.order-machine").children().length == 0) - old_region_item.children("div.empty-machine-msg").show(); - else - old_region_item.children("div.empty-machine-msg").hide(); - - new_region_item = $("#"+REGION_MAP[value]+"_body"); - new_region_item.children("div.empty-machine-msg").hide(); - } - else - item.find("."+field).val(value); - } - - function move_item(region_id, item) { - poorify_rich(item); - item.fadeOut(800, function() { - add_fieldset(region_id, item, {where:'append'}); - richify_poor(item); - update_item_controls(item, region_id); - item.show(); - }); - } - - function poorify_rich(item){ - item.children(".item-content").hide(); - - for (var i=0; i v2 ? 1 : -1; + } + + function create_new_spare_form(modvar) { + let old_form_count = parseInt( + $("#id_" + modvar + "_set-TOTAL_FORMS").val(), + 10 + ); + // **** UGLY CODE WARNING, avert your gaze! **** + // for some unknown reason, the add-button click handler function + // fails on the first triggerHandler call in some rare cases; + // we can detect this here and retry: + for (let i = 0; i < 2; i++) { + // Use Django's built-in inline spawing mechanism (Django 1.2+) + // must use django.jQuery since the bound function lives there: + django + .jQuery("#" + modvar + "_set-group") + .find(".add-row a") + .triggerHandler("click"); + let new_form_count = parseInt( + $("#id_" + modvar + "_set-TOTAL_FORMS").val(), + 10 + ); + if (new_form_count > old_form_count) { + return $("#" + modvar + "_set-" + (new_form_count - 1)); + } } - - function give_ordering_to_content_types() { - for (var i=0; i v2 ? 1 : -1; + } + + function give_ordering_to_content_types() { + for (let i = 0; i < REGION_MAP.length; i++) { + let container = $("#" + REGION_MAP[i] + "_body div.order-machine"); + for (let j = 0; j < container.children().length; j++) { + set_item_field_value( + container.find("fieldset.order-item:eq(" + j + ")"), + "order-field", + j + ); } } - - function order_content_types_in_regions() { - for (var i=0; i