diff --git a/README.md b/README.md index d60cf6b1..bb1ca115 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,81 @@ +# 💉✅ Django Unicorn Loosened + +This is a repository of a customized version of [Django Unicorn](https://github.com/adamghill/django-unicorn) package. + +_Loosened_ version of package is by default functionally the same as the official package. Extended stuff that were added are controllable with settings/feature flags. + +This repository is intended to sync periodically with `adamghill` repository in order to be up to date with the latest changes. + +Releasing as an installable package is planned only for a private repo for now, and version numbers will follow an official one with one extra dotted number. In the example, if there is an official version of `0.50.0`, the tag that follows this version here will be `0.50.0.1`. + +## Additional settings + +Available additional settings that can be set to `UNICORN` dict in settings.py which are not part of official package. + +- `USE_CSRF_TOKEN` - default: `True` - If set to `False`, unicorn does not check or send `csrf` token value so `{% csrf_token %}` is not mandatory in the templates. This is added due the fact to additional page caching system like `Varnish` does not operate effective if `Cookie` value is present in `Vary` header. +- `CHECK_CHECKSUM_MATCH` - default: `True` - If set to `False`, `unicorn` does not perform data checksum check on each request. + +## Deployment + +1. Add your repository to poetry.config: + `poetry config repositories.myrepo http://to.my.repo` + +2. Publish package to your repository with `--build` flag + `poetry publish --build -r myrepo -u -p ` + +## Customization changelog + +### 0.61.0.1 - (2024-07-16) + +- Avoid recursion upon caching parent/child complex components with `pickle.dumps` +- Sync with main package, version `0.61.0` + +### 0.60.0.1 - (2024-03-29) + +- No customizations, just sync with main package. + +### 0.58.1.2 - (2024-01-10) + +- add optional `CHECK_CHECKSUM_MATCH` setting which is set by default to True. If turned off, `unicorn` does not perform data checksum check on each request. + +### 0.58.1.1 - (2024-01-10) + +- No customizations, just sync with main package. + +### 0.57.1.1 - (2023-11-10) + +- No customizations, just sync with main package. + +### 0.55.0.1 - (2023-09-15) + +- No customizations, just sync with main package. + +### 0.54.0.1 - (2023-08-31) + +- No customizations, just sync with main package. + +### 0.53.0.1 - (2023-08-08) + +- No customizations, just sync with main package. + +### 0.50.1.1 - (2023-05-23) + +- No customizations, just sync with main package. + +### 0.50.0.1 - (2023-05-11) + +- Add `USE_CSRF_TOKEN` (`useCsrfToken`) setting that if set to `False` (`false`) avoids CSRF token check. By default `USE_CSRF_TOKEN` is set to `True`. +- [views.__init__.py:message] - Added decorator `csrf_handle` that checks `USE_CSRF_TOKEN` setting and based on boolean applies `csrf_protect` or `csrf_exempt` decorator. +- [templatetags/unicorn.py:unicorn_scripts] - Added `USE_CSRF_TOKEN` to return in order to use in templates +- [templates/unicorn/scripts.html] - Translate `USE_CSRF_TOKEN` value into `useCsrfToken` javascript variable and pass it to `Unicorn.init` +- [static/unicorn/js/unicorn.js:init] - apply `useCsrfToken` to `args` that are used latter in component. +- [static/unicorn/js/component.js:Component] - add `useCsrfToken` to class instance in constructor +- [static/unicorn/js/messageSender.js:send] - set `csrf` token to headers only if `useCsrfToken` is set to `true`. + +-------------------------------- +## Official documentation below +-------------------------------- +

django-unicorn logo

diff --git a/django_unicorn/cacher.py b/django_unicorn/cacher.py index b0a31441..1f905323 100644 --- a/django_unicorn/cacher.py +++ b/django_unicorn/cacher.py @@ -86,6 +86,10 @@ def __enter__(self): raise UnicornCacheError( f"Cannot cache component '{type(component)}' because it is not picklable: {type(e)}: {e}" ) from e + except RecursionError as e: + logger.warning( + f"Cannot cache component '{type(component)}' because it is not picklable: {type(e)}: {e}" + ) return self diff --git a/django_unicorn/components/unicorn_template_response.py b/django_unicorn/components/unicorn_template_response.py index eb9f3014..608dae29 100644 --- a/django_unicorn/components/unicorn_template_response.py +++ b/django_unicorn/components/unicorn_template_response.py @@ -247,6 +247,11 @@ def render(self): for child in descendant.children: init_script = f"{init_script} {child._init_script}" json_tags.append(child._json_tag) + # We need to delete this property here as it can cause RecursionError + # when pickling child component. Tag element has previous_sibling + # and next_sibling which would also be pickled and if they are big, + # cause RecursionError + del child._json_tag descendants.append(child) script_tag = soup.new_tag("script") diff --git a/django_unicorn/static/unicorn/js/component.js b/django_unicorn/static/unicorn/js/component.js index a3f00155..5102861c 100644 --- a/django_unicorn/static/unicorn/js/component.js +++ b/django_unicorn/static/unicorn/js/component.js @@ -25,6 +25,7 @@ export class Component { this.key = args.key; this.messageUrl = args.messageUrl; this.csrfTokenHeaderName = args.csrfTokenHeaderName; + this.useCsrfToken = args.useCsrfToken; this.csrfTokenCookieName = args.csrfTokenCookieName; this.hash = args.hash; this.data = args.data || {}; diff --git a/django_unicorn/static/unicorn/js/messageSender.js b/django_unicorn/static/unicorn/js/messageSender.js index 6d66e368..d6fc464b 100644 --- a/django_unicorn/static/unicorn/js/messageSender.js +++ b/django_unicorn/static/unicorn/js/messageSender.js @@ -37,7 +37,18 @@ export function send(component, callback) { Accept: "application/json", "X-Requested-With": "XMLHttpRequest", }; - headers[component.csrfTokenHeaderName] = getCsrfToken(component); + /** + * Override Reason: + Optionally ignore `CSRF` check for effective varnish caching + remove `Cookie` from `Vary` header. + */ + if (component.useCsrfToken) { + try { + headers[component.csrfTokenHeaderName] = getCsrfToken(component); + } catch (err) { + console.error(err); + } + } fetch(component.syncUrl, { method: "POST", diff --git a/django_unicorn/static/unicorn/js/unicorn.js b/django_unicorn/static/unicorn/js/unicorn.js index a80cff28..2350240a 100644 --- a/django_unicorn/static/unicorn/js/unicorn.js +++ b/django_unicorn/static/unicorn/js/unicorn.js @@ -5,6 +5,7 @@ import { getMorpher } from "./morpher.js"; let messageUrl = ""; let csrfTokenHeaderName = "X-CSRFToken"; +let useCsrfToken = true; let csrfTokenCookieName = "csrftoken"; let morpher; @@ -17,7 +18,8 @@ export function init( _messageUrl, _csrfTokenHeaderName, _csrfTokenCookieName, - _morpherSettings + _morpherSettings, + _useCsrfToken ) { messageUrl = _messageUrl; @@ -27,6 +29,13 @@ export function init( csrfTokenHeaderName = _csrfTokenHeaderName; } + /** + * Extend Reason: + Optionally ignore `CSRF` check for effective varnish caching + remove `Cookie` from `Vary` header by setting `useCsrfToken` to false + */ + if (_useCsrfToken !== undefined) useCsrfToken = _useCsrfToken; + if (hasValue(_csrfTokenCookieName)) { csrfTokenCookieName = _csrfTokenCookieName; } @@ -36,6 +45,7 @@ export function init( csrfTokenHeaderName, csrfTokenCookieName, morpher, + useCsrfToken }; } @@ -47,6 +57,7 @@ export function componentInit(args) { args.csrfTokenHeaderName = csrfTokenHeaderName; args.csrfTokenCookieName = csrfTokenCookieName; args.morpher = morpher; + args.useCsrfToken = useCsrfToken; const component = new Component(args); components[component.id] = component; diff --git a/django_unicorn/static/unicorn/js/unicorn.min.js b/django_unicorn/static/unicorn/js/unicorn.min.js index edcfb2c4..cbca314e 100644 --- a/django_unicorn/static/unicorn/js/unicorn.min.js +++ b/django_unicorn/static/unicorn/js/unicorn.min.js @@ -1,2 +1,2 @@ /* Version: 0.61.0 */ -var Unicorn=function(e){"use strict";function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){for(var i=0;ie.length)&&(t=e.length);for(var i=0,n=new Array(t);i-1}function h(e,t){return void 0===t&&(t=document),t.querySelector(e)}var d={acceptNode:function(e){return NodeFilter.FILTER_ACCEPT}},f={acceptNode:function(e){return e.getAttribute("unicorn:checksum")?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}};function m(e,t){for(var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:d,n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,i,!1);n.nextNode();)t(n.currentNode)}var p=function(){function e(i){t(this,e),this.attribute=i,this.name=this.attribute.name,this.value=this.attribute.value,this.isUnicorn=!1,this.isModel=!1,this.isPoll=!1,this.isLoading=!1,this.isTarget=!1,this.isPartial=!1,this.isDirty=!1,this.isVisible=!1,this.isKey=!1,this.isError=!1,this.modifiers={},this.eventType=null,this.init()}return n(e,[{key:"init",value:function(){var e=this;if(this.name.startsWith("unicorn:")||this.name.startsWith("u:")){if(this.isUnicorn=!0,c(this.name,":model"))this.isModel=!0;else if(c(this.name,":poll.disable"))this.isPollDisable=!0;else if(c(this.name,":poll"))this.isPoll=!0;else if(c(this.name,":loading"))this.isLoading=!0;else if(c(this.name,":target"))this.isTarget=!0;else if(c(this.name,":partial"))this.isPartial=!0;else if(c(this.name,":dirty"))this.isDirty=!0;else if(c(this.name,":visible"))this.isVisible=!0;else if("unicorn:key"===this.name||"u:key"===this.name)this.isKey=!0;else if(c(this.name,":error:"))this.isError=!0;else{var t=this.name.replace("unicorn:","").replace("u:","");"id"!==t&&"name"!==t&&"checksum"!==t&&(this.eventType=t)}var i=this.name;this.eventType&&(i=this.eventType),i.split(".").slice(1).forEach((function(t){var i=t.split("-");e.modifiers[i[0]]=!(i.length>1)||i[1],e.eventType&&(e.eventType=e.eventType.replace(".".concat(t),""))}))}}}]),e}(),v=function(){function e(i){t(this,e),this.el=i,this.init()}return n(e,[{key:"init",value:function(){var t=this;if(this.id=this.el.id,this.isUnicorn=!1,this.attributes=[],this.value=this.getValue(),this.parent=null,this.el.parentElement&&(this.parent=new e(this.el.parentElement)),this.model={},this.poll={},this.loading={},this.dirty={},this.actions=[],this.partials=[],this.target=null,this.visibility={},this.key=null,this.events=[],this.errors=[],this.el.attributes)for(var i=function(e){var i=new p(t.el.attributes[e]);if(t.attributes.push(i),i.isUnicorn&&(t.isUnicorn=!0),i.isModel){var n="model";t[n].name=i.value,t[n].eventType=i.modifiers.lazy?"blur":"input",t[n].isLazy=!!i.modifiers.lazy,t[n].isDefer=!!i.modifiers.defer,t[n].debounceTime=i.modifiers.debounce&&parseInt(i.modifiers.debounce,10)||-1}else if(i.isPoll){t.poll.method=i.value?i.value:"refresh",t.poll.timing=2e3,t.poll.disable=!1;var r=i.name.split("-").slice(1);r.length>0&&(t.poll.timing=parseInt(r[0],10)||2e3)}else if(i.isPollDisable)t.poll.disableData=i.value;else if(i.isLoading||i.isDirty){var o="dirty";i.isLoading&&(o="loading"),i.modifiers.attr?t[o].attr=i.value:i.modifiers.class&&i.modifiers.remove?t[o].removeClasses=i.value.split(" "):i.modifiers.class?t[o].classes=i.value.split(" "):i.isLoading&&i.modifiers.remove?t.loading.hide=!0:i.isLoading&&(t.loading.show=!0)}else if(i.isTarget)t.target=i.value;else if(i.isPartial)i.modifiers.id?t.partials.push({id:i.value}):i.modifiers.key?t.partials.push({key:i.value}):t.partials.push({target:i.value});else if(i.isVisible){var a=i.modifiers.threshold||0;a>1&&(a/=100),t.visibility.method=i.value,t.visibility.threshold=a,t.visibility.debounceTime=i.modifiers.debounce&&parseInt(i.modifiers.debounce,10)||0}else if(i.eventType){var s={};s.name=i.value,s.eventType=i.eventType,s.isPrevent=!1,s.isStop=!1,s.isDiscard=!1,s.debounceTime=0,i.modifiers&&Object.keys(i.modifiers).forEach((function(e){"prevent"===e?s.isPrevent=!0:"stop"===e?s.isStop=!0:"discard"===e?s.isDiscard=!0:"debounce"===e?s.debounceTime=i.modifiers.debounce&&parseInt(i.modifiers.debounce,10)||0:s.key=e})),t.actions.push(s)}if(i.isKey&&(t.key=i.value),i.isError){var l=i.name.replace("unicorn:error:","");t.errors.push({code:l,message:i.value})}},n=0;n0&&t in e.actionEvents&&e.actionEvents[t].forEach((function(t){var r=t.action,o=t.element;if(n.isSame(o)){e.walker(o.el,(function(t){e.modelEls.filter((function(e){return e.isSameEl(t)})).forEach((function(t){if(l(t.model)&&t.model.isLazy){var i={type:"syncInput",payload:{name:t.model.name,value:t.getValue()}};e.actionQueue.push(i)}}))})),r.isPrevent&&i.preventDefault(),r.isStop&&i.stopPropagation(),r.isDiscard&&(e.actionQueue=[]);var a=r.name;(function(e){if(!c(e=e.trim(),"(")||!e.endsWith(")"))return[];e=e.slice(e.indexOf("(")+1,e.length-1);for(var t=[],i="",n=!1,r=!1,o=0,a=0,s=0;s-1&&e.actionQueue.splice(a))}e.actionQueue.push(o),e.queueMessage(t.model.debounceTime,(function(i,n,r){r?console.error(r):((i=i||[]).some((function(e){return e.isSame(t)}))||i.push(t),e.setModelValues(i,n,!0))}))}))}var k={},T={};function w(e,t){if(0!==e.actionQueue.length&&e.currentActionQueue!==e.actionQueue){var i=e.actionQueue.some((function(e){return"callMethod"===e.type}));e.currentActionQueue=e.actionQueue,e.actionQueue=[];var n={id:e.id,data:e.data,checksum:e.checksum,actionQueue:e.currentActionQueue,epoch:Date.now(),hash:e.hash},r={Accept:"application/json","X-Requested-With":"XMLHttpRequest"};r[e.csrfTokenHeaderName]=function(e){var t=e.csrfTokenCookieName+"=",i=e.document.cookie.split(";").filter((function(e){return e.trim().startsWith(t)}));if(i.length>0)return i[0].replace(t,"");var n=e.document.getElementsByName("csrfmiddlewaretoken");if(n&&n.length>0)return n[0].getAttribute("value");throw Error("CSRF token is missing. Do you need to add {% csrf_token %}?")}(e),fetch(e.syncUrl,{method:"POST",headers:r,body:JSON.stringify(n)}).then((function(t){if(t.ok)return t.json();if(e.loadingEls.forEach((function(e){e.loading.hide?e.show():e.loading.show&&e.hide(),e.handleLoading(!0),e.handleDirty(!0)})),304===t.status)return null;throw Error("Error when getting response: ".concat(t.statusText," (").concat(t.status,")"))})).then((function(n){if(n&&(!n.queued||!0!==n.queued)){if(n.error)throw"Checksum does not match"===n.error&&u(t)&&t([],!0,null),Error(n.error);if(n.redirect)if(n.redirect.url){if(!n.redirect.refresh)return void(e.window.location.href=n.redirect.url);n.redirect.title&&(e.window.document.title=n.redirect.title),e.window.history.pushState({},"",n.redirect.url)}else n.redirect.hash&&(e.window.location.hash=n.redirect.hash);e.modelEls.forEach((function(e){e.init(),e.removeErrors(),e.handleDirty(!0)})),Object.keys(n.data||{}).forEach((function(t){e.data[t]=n.data[t]})),e.errors=n.errors||{},e.return=n.return||{},e.hash=n.hash;var r=n.parent||{},o=n.dom||"",a=n.partials||[],s=n.checksum,c=n.poll||{};for(l(c)&&(e.poll.timer&&clearInterval(e.poll.timer),c.timing&&(e.poll.timing=c.timing),c.method&&(e.poll.method=c.method),e.poll.disable=c.disable||!1,e.startPolling());l(r)&&l(r.id);){var d=e.getParentComponent(r.id);d&&d.id===r.id&&(l(r.data)&&(d.data=r.data),r.dom&&(d.morphRoot(r.dom),d.loadingEls.forEach((function(e){e.loading.hide?e.show():e.loading.show&&e.hide(),e.handleLoading(!0),e.handleDirty(!0)}))),r.checksum&&(d.root.setAttribute("unicorn:checksum",r.checksum),d.refreshChecksum()),d.hash=r.hash,d.refreshEventListeners()),r=r.parent||{}}if(a.length>0){for(var f=0;f0){var o=!1;l(r=e.slice(-1)[0])&&l(r.model)&&!r.model.isLazy&&["id","key"].forEach((function(e){n.modelEls.forEach((function(t){o||r[e]&&r[e]===t[e]&&(t.focus(),o=!0)}))}))}if(this.modelEls.forEach((function(e){!t&&r&&r.isSame(e)||n.setValue(e)})),this.getChildrenComponents().forEach((function(i){i.setModelValues(e,t,!1)})),i){var a=this.getParentComponent();a&&a.setModelValues(e,t,i)}}},{key:"queueMessage",value:function(e,t){-1===e?a(w,250,!1)(this,t):a(w,e,!1)(this,t)}},{key:"triggerLifecycleEvent",value:function(e){var t=this;e in T&&T[e].forEach((function(e){return e(t)}))}},{key:"trigger",value:function(e){this.modelEls.forEach((function(t){if(t.key===e){var i=t.model.isLazy?"blur":"input";t.el.dispatchEvent(new Event(i))}}))}},{key:"morph",value:function(e,t){if(t){var i=function(){return r(e.querySelectorAll("[unicorn\\:id]"))},n=new Set(i().map((function(e){return e.getAttribute("unicorn:id")})));this.morpher.morph(e,t);var o=new Set(i().map((function(e){return e.getAttribute("unicorn:id")})));r(n).filter((function(e){return!o.has(e)})).forEach((function(e){Unicorn.deleteComponent(e)})),i().forEach((function(e){Unicorn.insertComponentFromDom(e)}))}}},{key:"morphRoot",value:function(e){this.morph(this.root,e)}}]),e}();var C="undefined"==typeof document?void 0:document,S=!!C&&"content"in C.createElement("template"),L=!!C&&C.createRange&&"createContextualFragment"in C.createRange();function P(e){return e=e.trim(),S?function(e){var t=C.createElement("template");return t.innerHTML=e,t.content.childNodes[0]}(e):L?function(e){return A||(A=C.createRange()).selectNode(C.body),A.createContextualFragment(e).childNodes[0]}(e):function(e){var t=C.createElement("body");return t.innerHTML=e,t.childNodes[0]}(e)}function O(e,t){var i,n,r=e.nodeName,o=t.nodeName;return r===o||(i=r.charCodeAt(0),n=o.charCodeAt(0),i<=90&&n>=97?r===o.toUpperCase():n<=90&&i>=97&&o===r.toUpperCase())}function M(e,t,i){e[i]!==t[i]&&(e[i]=t[i],e[i]?e.setAttribute(i,""):e.removeAttribute(i))}var U={OPTION:function(e,t){var i=e.parentNode;if(i){var n=i.nodeName.toUpperCase();"OPTGROUP"===n&&(n=(i=i.parentNode)&&i.nodeName.toUpperCase()),"SELECT"!==n||i.hasAttribute("multiple")||(e.hasAttribute("selected")&&!t.selected&&(e.setAttribute("selected","selected"),e.removeAttribute("selected")),i.selectedIndex=-1)}M(e,t,"selected")},INPUT:function(e,t){M(e,t,"checked"),M(e,t,"disabled"),e.value!==t.value&&(e.value=t.value),t.hasAttribute("value")||e.removeAttribute("value")},TEXTAREA:function(e,t){var i=t.value;e.value!==i&&(e.value=i);var n=e.firstChild;if(n){var r=n.nodeValue;if(r==i||!i&&r==e.placeholder)return;n.nodeValue=i}},SELECT:function(e,t){if(!t.hasAttribute("multiple")){for(var i,n,r=-1,o=0,a=e.firstChild;a;)if("OPTGROUP"===(n=a.nodeName&&a.nodeName.toUpperCase()))a=(i=a).firstChild;else{if("OPTION"===n){if(a.hasAttribute("selected")){r=o;break}o++}!(a=a.nextSibling)&&i&&(a=i.nextSibling,i=null)}e.selectedIndex=r}}};function V(){}function D(e){if(e)return e.getAttribute&&e.getAttribute("id")||e.id}var I=function(e){return function(t,i,n){if(n||(n={}),"string"==typeof i)if("#document"===t.nodeName||"HTML"===t.nodeName||"BODY"===t.nodeName){var r=i;(i=C.createElement("html")).innerHTML=r}else i=P(i);var o=n.getNodeKey||D,a=n.onBeforeNodeAdded||V,s=n.onNodeAdded||V,l=n.onBeforeElUpdated||V,u=n.onElUpdated||V,c=n.onBeforeNodeDiscarded||V,h=n.onNodeDiscarded||V,d=n.onBeforeElChildrenUpdated||V,f=!0===n.childrenOnly,m=Object.create(null),p=[];function v(e){p.push(e)}function y(e,t){if(1===e.nodeType)for(var i=e.firstChild;i;){var n=void 0;t&&(n=o(i))?v(n):(h(i),i.firstChild&&y(i,t)),i=i.nextSibling}}function g(e,t,i){!1!==c(e)&&(t&&t.removeChild(e),h(e),y(e,i))}function b(e){s(e);for(var t=e.firstChild;t;){var i=t.nextSibling,n=o(t);if(n){var r=m[n];r&&O(t,r)?(t.parentNode.replaceChild(r,t),E(r,t)):b(t)}else b(t);t=i}}function E(t,i,n){var r=o(i);if(r&&delete m[r],!n){if(!1===l(t,i))return;if(t.hasAttribute("u:ignore")||t.hasAttribute("unicorn:ignore"))return;if(e(t,i),u(t),!1===d(t,i))return}"TEXTAREA"!==t.nodeName?function(e,t){var i,n,r,s,l,u=t.firstChild,c=e.firstChild;e:for(;u;){for(s=u.nextSibling,i=o(u);c;){if(r=c.nextSibling,u.isSameNode&&u.isSameNode(c)){u=s,c=r;continue e}n=o(c);var h=c.nodeType,d=void 0;if(h===u.nodeType&&(1===h?(i?i!==n&&((l=m[i])?r===l?d=!1:(e.insertBefore(l,c),n?v(n):g(c,e,!0),c=l):d=!1):n&&(d=!1),(d=!1!==d&&O(c,u))&&E(c,u)):3!==h&&8!=h||(d=!0,c.nodeValue!==u.nodeValue&&(c.nodeValue=u.nodeValue))),d){u=s,c=r;continue e}n?v(n):g(c,e,!0),c=r}if(i&&(l=m[i])&&O(l,u))e.appendChild(l),E(l,u);else{var f=a(u);!1!==f&&(f&&(u=f),u.actualize&&(u=u.actualize(e.ownerDocument||C)),e.appendChild(u),b(u))}u=s,c=r}!function(e,t,i){for(;t;){var n=t.nextSibling;(i=o(t))?v(i):g(t,e,!0),t=n}}(e,c,n);var p=U[e.nodeName];p&&p(e,t)}(t,i):t.innerHTML!=i.innerHTML&&U.TEXTAREA(t,i)}!function e(t){if(1===t.nodeType||11===t.nodeType)for(var i=t.firstChild;i;){var n=o(i);n&&(m[n]=i),e(i),i=i.nextSibling}}(t);var k,T,w=t,A=w.nodeType,N=i.nodeType;if(!f)if(1===A)1===N?O(t,i)||(h(t),w=function(e,t){for(var i=e.firstChild;i;){var n=i.nextSibling;t.appendChild(i),i=n}return t}(t,(k=i.nodeName,(T=i.namespaceURI)&&"http://www.w3.org/1999/xhtml"!==T?C.createElementNS(T,k):C.createElement(k)))):w=i;else if(3===A||8===A){if(N===A)return w.nodeValue!==i.nodeValue&&(w.nodeValue=i.nodeValue),w;w=i}if(w===i)h(t);else{if(i.isSameNode&&i.isSameNode(w))return;if(E(w,i,f),p)for(var S=0,L=p.length;S=0;s--)n=(i=a[s]).name,r=i.namespaceURI,o=i.value,r?(n=i.localName||n,e.getAttributeNS(r,n)!==o&&("xmlns"===i.prefix&&(n=i.name),e.setAttributeNS(r,n,o))):e.getAttribute(n)!==o&&e.setAttribute(n,o);for(var l=e.attributes,u=l.length-1;u>=0;u--)n=(i=l[u]).name,(r=i.namespaceURI)?(n=i.localName||n,t.hasAttributeNS(r,n)||e.removeAttributeNS(r,n)):t.hasAttribute(n)||e.removeAttribute(n)}})),R={morphdom:function(){function e(i){t(this,e),this.options=i}return n(e,[{key:"morph",value:function(e,t){return I(e,t,this.getOptions())}},{key:"getOptions",value:function(){var e=this.options.RELOAD_SCRIPT_ELEMENTS||!1;return{childrenOnly:!1,getNodeKey:function(e){if(e.attributes){var t=e.getAttribute("unicorn:id")||e.getAttribute("unicorn:key")||e.id;if(t)return t}},onBeforeElUpdated:function(t,i){if(t.isEqualNode(i))return!1;if(e&&"SCRIPT"===t.nodeName&&"SCRIPT"===i.nodeName){var n=document.createElement("script");return r(i.attributes).forEach((function(e){n.setAttribute(e.nodeName,e.nodeValue)})),n.innerHTML=i.innerHTML,t.replaceWith(n),!1}return!0},onNodeAdded:function(t){if(e&&"SCRIPT"===t.nodeName){var i=document.createElement("script");r(t.attributes).forEach((function(e){i.setAttribute(e.nodeName,e.nodeValue)})),i.innerHTML=t.innerHTML,t.replaceWith(i)}}}}}]),e}(),alpine:function(){function e(i){t(this,e),this.options=i}return n(e,[{key:"morph",value:function(e,t){if(t){if(!window.Alpine||!window.Alpine.morph)throw Error("\n Alpine.js and the Alpine morph plugin can not be found.\n See https://www.django-unicorn.com/docs/custom-morphers/#alpine for more information.\n ");return window.Alpine.morph(e,t,this.getOptions())}}},{key:"getOptions",value:function(){return{key:function(e){if(e.attributes){var t=e.getAttribute("unicorn:key")||e.getAttribute("u:key")||e.id;if(t)return t}return e.id}}}}]),e}()};var x,Q="",j="X-CSRFToken",H="csrftoken";function z(e){e.messageUrl=Q,e.csrfTokenHeaderName=j,e.csrfTokenCookieName=H,e.morpher=x;var t=new N(e);k[t.id]=t,t.setModelValues()}function F(e){var t;if(Object.keys(k).forEach((function(i){if(s(t)){var n=k[i];n.key===e&&(t=n)}})),s(t)&&Object.keys(k).forEach((function(i){if(s(t)){var n=k[i];n.name===e&&(t=n)}})),!t)throw Error("No component found for: ".concat(e));return t}return e.addEventListener=function(e,t){e in T||(T[e]=[]),T[e].push(t)},e.call=function(e,t){for(var i=F(e),n="",r=arguments.length,o=new Array(r>2?r-2:0),a=2;ae.length)&&(t=e.length);for(var i=0,n=new Array(t);i-1}function h(e,t){return void 0===t&&(t=document),t.querySelector(e)}var d={acceptNode:function(e){return NodeFilter.FILTER_ACCEPT}},f={acceptNode:function(e){return e.getAttribute("unicorn:checksum")?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}};function m(e,t){for(var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:d,n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,i,!1);n.nextNode();)t(n.currentNode)}var p=function(){function e(i){t(this,e),this.attribute=i,this.name=this.attribute.name,this.value=this.attribute.value,this.isUnicorn=!1,this.isModel=!1,this.isPoll=!1,this.isLoading=!1,this.isTarget=!1,this.isPartial=!1,this.isDirty=!1,this.isVisible=!1,this.isKey=!1,this.isError=!1,this.modifiers={},this.eventType=null,this.init()}return n(e,[{key:"init",value:function(){var e=this;if(this.name.startsWith("unicorn:")||this.name.startsWith("u:")){if(this.isUnicorn=!0,c(this.name,":model"))this.isModel=!0;else if(c(this.name,":poll.disable"))this.isPollDisable=!0;else if(c(this.name,":poll"))this.isPoll=!0;else if(c(this.name,":loading"))this.isLoading=!0;else if(c(this.name,":target"))this.isTarget=!0;else if(c(this.name,":partial"))this.isPartial=!0;else if(c(this.name,":dirty"))this.isDirty=!0;else if(c(this.name,":visible"))this.isVisible=!0;else if("unicorn:key"===this.name||"u:key"===this.name)this.isKey=!0;else if(c(this.name,":error:"))this.isError=!0;else{var t=this.name.replace("unicorn:","").replace("u:","");"id"!==t&&"name"!==t&&"checksum"!==t&&(this.eventType=t)}var i=this.name;this.eventType&&(i=this.eventType),i.split(".").slice(1).forEach((function(t){var i=t.split("-");e.modifiers[i[0]]=!(i.length>1)||i[1],e.eventType&&(e.eventType=e.eventType.replace(".".concat(t),""))}))}}}]),e}(),v=function(){function e(i){t(this,e),this.el=i,this.init()}return n(e,[{key:"init",value:function(){var t=this;if(this.id=this.el.id,this.isUnicorn=!1,this.attributes=[],this.value=this.getValue(),this.parent=null,this.el.parentElement&&(this.parent=new e(this.el.parentElement)),this.model={},this.poll={},this.loading={},this.dirty={},this.actions=[],this.partials=[],this.target=null,this.visibility={},this.key=null,this.events=[],this.errors=[],this.el.attributes)for(var i=function(e){var i=new p(t.el.attributes[e]);if(t.attributes.push(i),i.isUnicorn&&(t.isUnicorn=!0),i.isModel){var n="model";t[n].name=i.value,t[n].eventType=i.modifiers.lazy?"blur":"input",t[n].isLazy=!!i.modifiers.lazy,t[n].isDefer=!!i.modifiers.defer,t[n].debounceTime=i.modifiers.debounce&&parseInt(i.modifiers.debounce,10)||-1}else if(i.isPoll){t.poll.method=i.value?i.value:"refresh",t.poll.timing=2e3,t.poll.disable=!1;var r=i.name.split("-").slice(1);r.length>0&&(t.poll.timing=parseInt(r[0],10)||2e3)}else if(i.isPollDisable)t.poll.disableData=i.value;else if(i.isLoading||i.isDirty){var o="dirty";i.isLoading&&(o="loading"),i.modifiers.attr?t[o].attr=i.value:i.modifiers.class&&i.modifiers.remove?t[o].removeClasses=i.value.split(" "):i.modifiers.class?t[o].classes=i.value.split(" "):i.isLoading&&i.modifiers.remove?t.loading.hide=!0:i.isLoading&&(t.loading.show=!0)}else if(i.isTarget)t.target=i.value;else if(i.isPartial)i.modifiers.id?t.partials.push({id:i.value}):i.modifiers.key?t.partials.push({key:i.value}):t.partials.push({target:i.value});else if(i.isVisible){var a=i.modifiers.threshold||0;a>1&&(a/=100),t.visibility.method=i.value,t.visibility.threshold=a,t.visibility.debounceTime=i.modifiers.debounce&&parseInt(i.modifiers.debounce,10)||0}else if(i.eventType){var s={};s.name=i.value,s.eventType=i.eventType,s.isPrevent=!1,s.isStop=!1,s.isDiscard=!1,s.debounceTime=0,i.modifiers&&Object.keys(i.modifiers).forEach((function(e){"prevent"===e?s.isPrevent=!0:"stop"===e?s.isStop=!0:"discard"===e?s.isDiscard=!0:"debounce"===e?s.debounceTime=i.modifiers.debounce&&parseInt(i.modifiers.debounce,10)||0:s.key=e})),t.actions.push(s)}if(i.isKey&&(t.key=i.value),i.isError){var l=i.name.replace("unicorn:error:","");t.errors.push({code:l,message:i.value})}},n=0;n0&&t in e.actionEvents&&e.actionEvents[t].forEach((function(t){var r=t.action,o=t.element;if(n.isSame(o)){e.walker(o.el,(function(t){e.modelEls.filter((function(e){return e.isSameEl(t)})).forEach((function(t){if(l(t.model)&&t.model.isLazy){var i={type:"syncInput",payload:{name:t.model.name,value:t.getValue()}};e.actionQueue.push(i)}}))})),r.isPrevent&&i.preventDefault(),r.isStop&&i.stopPropagation(),r.isDiscard&&(e.actionQueue=[]);var a=r.name;(function(e){if(!c(e=e.trim(),"(")||!e.endsWith(")"))return[];e=e.slice(e.indexOf("(")+1,e.length-1);for(var t=[],i="",n=!1,r=!1,o=0,a=0,s=0;s-1&&e.actionQueue.splice(a))}e.actionQueue.push(o),e.queueMessage(t.model.debounceTime,(function(i,n,r){r?console.error(r):((i=i||[]).some((function(e){return e.isSame(t)}))||i.push(t),e.setModelValues(i,n,!0))}))}))}var k={},T={};function w(e,t){if(0!==e.actionQueue.length&&e.currentActionQueue!==e.actionQueue){var i=e.actionQueue.some((function(e){return"callMethod"===e.type}));e.currentActionQueue=e.actionQueue,e.actionQueue=[];var n={id:e.id,data:e.data,checksum:e.checksum,actionQueue:e.currentActionQueue,epoch:Date.now(),hash:e.hash},r={Accept:"application/json","X-Requested-With":"XMLHttpRequest"};if(e.useCsrfToken)try{r[e.csrfTokenHeaderName]=function(e){var t=e.csrfTokenCookieName+"=",i=e.document.cookie.split(";").filter((function(e){return e.trim().startsWith(t)}));if(i.length>0)return i[0].replace(t,"");var n=e.document.getElementsByName("csrfmiddlewaretoken");if(n&&n.length>0)return n[0].getAttribute("value");throw Error("CSRF token is missing. Do you need to add {% csrf_token %}?")}(e)}catch(e){console.error(e)}fetch(e.syncUrl,{method:"POST",headers:r,body:JSON.stringify(n)}).then((function(t){if(t.ok)return t.json();if(e.loadingEls.forEach((function(e){e.loading.hide?e.show():e.loading.show&&e.hide(),e.handleLoading(!0),e.handleDirty(!0)})),304===t.status)return null;throw Error("Error when getting response: ".concat(t.statusText," (").concat(t.status,")"))})).then((function(n){if(n&&(!n.queued||!0!==n.queued)){if(n.error)throw"Checksum does not match"===n.error&&u(t)&&t([],!0,null),Error(n.error);if(n.redirect)if(n.redirect.url){if(!n.redirect.refresh)return void(e.window.location.href=n.redirect.url);n.redirect.title&&(e.window.document.title=n.redirect.title),e.window.history.pushState({},"",n.redirect.url)}else n.redirect.hash&&(e.window.location.hash=n.redirect.hash);e.modelEls.forEach((function(e){e.init(),e.removeErrors(),e.handleDirty(!0)})),Object.keys(n.data||{}).forEach((function(t){e.data[t]=n.data[t]})),e.errors=n.errors||{},e.return=n.return||{},e.hash=n.hash;var r=n.parent||{},o=n.dom||"",a=n.partials||[],s=n.checksum,c=n.poll||{};for(l(c)&&(e.poll.timer&&clearInterval(e.poll.timer),c.timing&&(e.poll.timing=c.timing),c.method&&(e.poll.method=c.method),e.poll.disable=c.disable||!1,e.startPolling());l(r)&&l(r.id);){var d=e.getParentComponent(r.id);d&&d.id===r.id&&(l(r.data)&&(d.data=r.data),r.dom&&(d.morphRoot(r.dom),d.loadingEls.forEach((function(e){e.loading.hide?e.show():e.loading.show&&e.hide(),e.handleLoading(!0),e.handleDirty(!0)}))),r.checksum&&(d.root.setAttribute("unicorn:checksum",r.checksum),d.refreshChecksum()),d.hash=r.hash,d.refreshEventListeners()),r=r.parent||{}}if(a.length>0){for(var f=0;f0){var o=!1;l(r=e.slice(-1)[0])&&l(r.model)&&!r.model.isLazy&&["id","key"].forEach((function(e){n.modelEls.forEach((function(t){o||r[e]&&r[e]===t[e]&&(t.focus(),o=!0)}))}))}if(this.modelEls.forEach((function(e){!t&&r&&r.isSame(e)||n.setValue(e)})),this.getChildrenComponents().forEach((function(i){i.setModelValues(e,t,!1)})),i){var a=this.getParentComponent();a&&a.setModelValues(e,t,i)}}},{key:"queueMessage",value:function(e,t){-1===e?a(w,250,!1)(this,t):a(w,e,!1)(this,t)}},{key:"triggerLifecycleEvent",value:function(e){var t=this;e in T&&T[e].forEach((function(e){return e(t)}))}},{key:"trigger",value:function(e){this.modelEls.forEach((function(t){if(t.key===e){var i=t.model.isLazy?"blur":"input";t.el.dispatchEvent(new Event(i))}}))}},{key:"morph",value:function(e,t){if(t){var i=function(){return r(e.querySelectorAll("[unicorn\\:id]"))},n=new Set(i().map((function(e){return e.getAttribute("unicorn:id")})));this.morpher.morph(e,t);var o=new Set(i().map((function(e){return e.getAttribute("unicorn:id")})));r(n).filter((function(e){return!o.has(e)})).forEach((function(e){Unicorn.deleteComponent(e)})),i().forEach((function(e){Unicorn.insertComponentFromDom(e)}))}}},{key:"morphRoot",value:function(e){this.morph(this.root,e)}}]),e}();var C="undefined"==typeof document?void 0:document,S=!!C&&"content"in C.createElement("template"),L=!!C&&C.createRange&&"createContextualFragment"in C.createRange();function P(e){return e=e.trim(),S?function(e){var t=C.createElement("template");return t.innerHTML=e,t.content.childNodes[0]}(e):L?function(e){return A||(A=C.createRange()).selectNode(C.body),A.createContextualFragment(e).childNodes[0]}(e):function(e){var t=C.createElement("body");return t.innerHTML=e,t.childNodes[0]}(e)}function O(e,t){var i,n,r=e.nodeName,o=t.nodeName;return r===o||(i=r.charCodeAt(0),n=o.charCodeAt(0),i<=90&&n>=97?r===o.toUpperCase():n<=90&&i>=97&&o===r.toUpperCase())}function M(e,t,i){e[i]!==t[i]&&(e[i]=t[i],e[i]?e.setAttribute(i,""):e.removeAttribute(i))}var U={OPTION:function(e,t){var i=e.parentNode;if(i){var n=i.nodeName.toUpperCase();"OPTGROUP"===n&&(n=(i=i.parentNode)&&i.nodeName.toUpperCase()),"SELECT"!==n||i.hasAttribute("multiple")||(e.hasAttribute("selected")&&!t.selected&&(e.setAttribute("selected","selected"),e.removeAttribute("selected")),i.selectedIndex=-1)}M(e,t,"selected")},INPUT:function(e,t){M(e,t,"checked"),M(e,t,"disabled"),e.value!==t.value&&(e.value=t.value),t.hasAttribute("value")||e.removeAttribute("value")},TEXTAREA:function(e,t){var i=t.value;e.value!==i&&(e.value=i);var n=e.firstChild;if(n){var r=n.nodeValue;if(r==i||!i&&r==e.placeholder)return;n.nodeValue=i}},SELECT:function(e,t){if(!t.hasAttribute("multiple")){for(var i,n,r=-1,o=0,a=e.firstChild;a;)if("OPTGROUP"===(n=a.nodeName&&a.nodeName.toUpperCase()))a=(i=a).firstChild;else{if("OPTION"===n){if(a.hasAttribute("selected")){r=o;break}o++}!(a=a.nextSibling)&&i&&(a=i.nextSibling,i=null)}e.selectedIndex=r}}};function V(){}function D(e){if(e)return e.getAttribute&&e.getAttribute("id")||e.id}var I=function(e){return function(t,i,n){if(n||(n={}),"string"==typeof i)if("#document"===t.nodeName||"HTML"===t.nodeName||"BODY"===t.nodeName){var r=i;(i=C.createElement("html")).innerHTML=r}else i=P(i);var o=n.getNodeKey||D,a=n.onBeforeNodeAdded||V,s=n.onNodeAdded||V,l=n.onBeforeElUpdated||V,u=n.onElUpdated||V,c=n.onBeforeNodeDiscarded||V,h=n.onNodeDiscarded||V,d=n.onBeforeElChildrenUpdated||V,f=!0===n.childrenOnly,m=Object.create(null),p=[];function v(e){p.push(e)}function y(e,t){if(1===e.nodeType)for(var i=e.firstChild;i;){var n=void 0;t&&(n=o(i))?v(n):(h(i),i.firstChild&&y(i,t)),i=i.nextSibling}}function g(e,t,i){!1!==c(e)&&(t&&t.removeChild(e),h(e),y(e,i))}function b(e){s(e);for(var t=e.firstChild;t;){var i=t.nextSibling,n=o(t);if(n){var r=m[n];r&&O(t,r)?(t.parentNode.replaceChild(r,t),E(r,t)):b(t)}else b(t);t=i}}function E(t,i,n){var r=o(i);if(r&&delete m[r],!n){if(!1===l(t,i))return;if(t.hasAttribute("u:ignore")||t.hasAttribute("unicorn:ignore"))return;if(e(t,i),u(t),!1===d(t,i))return}"TEXTAREA"!==t.nodeName?function(e,t){var i,n,r,s,l,u=t.firstChild,c=e.firstChild;e:for(;u;){for(s=u.nextSibling,i=o(u);c;){if(r=c.nextSibling,u.isSameNode&&u.isSameNode(c)){u=s,c=r;continue e}n=o(c);var h=c.nodeType,d=void 0;if(h===u.nodeType&&(1===h?(i?i!==n&&((l=m[i])?r===l?d=!1:(e.insertBefore(l,c),n?v(n):g(c,e,!0),c=l):d=!1):n&&(d=!1),(d=!1!==d&&O(c,u))&&E(c,u)):3!==h&&8!=h||(d=!0,c.nodeValue!==u.nodeValue&&(c.nodeValue=u.nodeValue))),d){u=s,c=r;continue e}n?v(n):g(c,e,!0),c=r}if(i&&(l=m[i])&&O(l,u))e.appendChild(l),E(l,u);else{var f=a(u);!1!==f&&(f&&(u=f),u.actualize&&(u=u.actualize(e.ownerDocument||C)),e.appendChild(u),b(u))}u=s,c=r}!function(e,t,i){for(;t;){var n=t.nextSibling;(i=o(t))?v(i):g(t,e,!0),t=n}}(e,c,n);var p=U[e.nodeName];p&&p(e,t)}(t,i):t.innerHTML!=i.innerHTML&&U.TEXTAREA(t,i)}!function e(t){if(1===t.nodeType||11===t.nodeType)for(var i=t.firstChild;i;){var n=o(i);n&&(m[n]=i),e(i),i=i.nextSibling}}(t);var k,T,w=t,A=w.nodeType,N=i.nodeType;if(!f)if(1===A)1===N?O(t,i)||(h(t),w=function(e,t){for(var i=e.firstChild;i;){var n=i.nextSibling;t.appendChild(i),i=n}return t}(t,(k=i.nodeName,(T=i.namespaceURI)&&"http://www.w3.org/1999/xhtml"!==T?C.createElementNS(T,k):C.createElement(k)))):w=i;else if(3===A||8===A){if(N===A)return w.nodeValue!==i.nodeValue&&(w.nodeValue=i.nodeValue),w;w=i}if(w===i)h(t);else{if(i.isSameNode&&i.isSameNode(w))return;if(E(w,i,f),p)for(var S=0,L=p.length;S=0;s--)n=(i=a[s]).name,r=i.namespaceURI,o=i.value,r?(n=i.localName||n,e.getAttributeNS(r,n)!==o&&("xmlns"===i.prefix&&(n=i.name),e.setAttributeNS(r,n,o))):e.getAttribute(n)!==o&&e.setAttribute(n,o);for(var l=e.attributes,u=l.length-1;u>=0;u--)n=(i=l[u]).name,(r=i.namespaceURI)?(n=i.localName||n,t.hasAttributeNS(r,n)||e.removeAttributeNS(r,n)):t.hasAttribute(n)||e.removeAttribute(n)}})),R={morphdom:function(){function e(i){t(this,e),this.options=i}return n(e,[{key:"morph",value:function(e,t){return I(e,t,this.getOptions())}},{key:"getOptions",value:function(){var e=this.options.RELOAD_SCRIPT_ELEMENTS||!1;return{childrenOnly:!1,getNodeKey:function(e){if(e.attributes){var t=e.getAttribute("unicorn:id")||e.getAttribute("unicorn:key")||e.id;if(t)return t}},onBeforeElUpdated:function(t,i){if(t.isEqualNode(i))return!1;if(e&&"SCRIPT"===t.nodeName&&"SCRIPT"===i.nodeName){var n=document.createElement("script");return r(i.attributes).forEach((function(e){n.setAttribute(e.nodeName,e.nodeValue)})),n.innerHTML=i.innerHTML,t.replaceWith(n),!1}return!0},onNodeAdded:function(t){if(e&&"SCRIPT"===t.nodeName){var i=document.createElement("script");r(t.attributes).forEach((function(e){i.setAttribute(e.nodeName,e.nodeValue)})),i.innerHTML=t.innerHTML,t.replaceWith(i)}}}}}]),e}(),alpine:function(){function e(i){t(this,e),this.options=i}return n(e,[{key:"morph",value:function(e,t){if(t){if(!window.Alpine||!window.Alpine.morph)throw Error("\n Alpine.js and the Alpine morph plugin can not be found.\n See https://www.django-unicorn.com/docs/custom-morphers/#alpine for more information.\n ");return window.Alpine.morph(e,t,this.getOptions())}}},{key:"getOptions",value:function(){return{key:function(e){if(e.attributes){var t=e.getAttribute("unicorn:key")||e.getAttribute("u:key")||e.id;if(t)return t}return e.id}}}}]),e}()};var x,Q="",j="X-CSRFToken",H=!0,z="csrftoken";function F(e){e.messageUrl=Q,e.csrfTokenHeaderName=j,e.csrfTokenCookieName=z,e.morpher=x,e.useCsrfToken=H;var t=new N(e);k[t.id]=t,t.setModelValues()}function W(e){var t;if(Object.keys(k).forEach((function(i){if(s(t)){var n=k[i];n.key===e&&(t=n)}})),s(t)&&Object.keys(k).forEach((function(i){if(s(t)){var n=k[i];n.name===e&&(t=n)}})),!t)throw Error("No component found for: ".concat(e));return t}return e.addEventListener=function(e,t){e in T||(T[e]=[]),T[e].push(t)},e.call=function(e,t){for(var i=W(e),n="",r=arguments.length,o=new Array(r>2?r-2:0),a=2;a + var useCsrfToken = "{{ USE_CSRF_TOKEN }}" == "True"; + {% if MINIFIED %} - -{% comment %} {% endcomment %} + {% else %} {% endif %} diff --git a/django_unicorn/templatetags/unicorn.py b/django_unicorn/templatetags/unicorn.py index a796079b..11a4cee1 100644 --- a/django_unicorn/templatetags/unicorn.py +++ b/django_unicorn/templatetags/unicorn.py @@ -31,6 +31,7 @@ def unicorn_scripts(): return { "MINIFIED": get_setting("MINIFIED", not settings.DEBUG), + "USE_CSRF_TOKEN": get_setting("USE_CSRF_TOKEN", True), "CSRF_HEADER_NAME": csrf_header_name, "CSRF_COOKIE_NAME": csrf_cookie_name, "MORPHER": get_morpher_settings(), diff --git a/django_unicorn/views/__init__.py b/django_unicorn/views/__init__.py index 4e7afe6c..35cea0e7 100644 --- a/django_unicorn/views/__init__.py +++ b/django_unicorn/views/__init__.py @@ -11,7 +11,7 @@ from django.http import HttpRequest, JsonResponse from django.http.response import HttpResponseNotModified from django.utils.safestring import mark_safe -from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie +from django.views.decorators.csrf import csrf_protect, csrf_exempt, ensure_csrf_cookie from django.views.decorators.http import require_POST from django_unicorn.components import UnicornView @@ -23,6 +23,7 @@ ) from django_unicorn.serializer import loads from django_unicorn.settings import ( + get_setting, get_cache_alias, get_serial_enabled, get_serial_timeout, @@ -523,13 +524,27 @@ def _handle_queued_component_requests(request: HttpRequest, queue_cache_key) -> return first_json_result +def csrf_handle(func): + """ + In case if `USE_CSRF_TOKEN` is set to False + CSRF token is not required. + """ + if get_setting("USE_CSRF_TOKEN", True): + return ensure_csrf_cookie(csrf_protect(func)) + return csrf_exempt(func) + + @timed @handle_error -@ensure_csrf_cookie -@csrf_protect +@csrf_handle @require_POST def message(request: HttpRequest, component_name: Optional[str] = None) -> JsonResponse: """ + Overwrite Reason: + Optionally Ignore `CSRF` check for effective varnish caching remove `Cookie` from `Vary` header. + Use `csrf_handle` decorator to optionally enable requests without `CSRF` token, + based on `USE_CSRF_TOKEN` setting. + ---- Endpoint that instantiates the component and does the correct action (set an attribute or call a method) depending on the JSON payload in the body. diff --git a/django_unicorn/views/objects.py b/django_unicorn/views/objects.py index c463faf3..14e203ba 100644 --- a/django_unicorn/views/objects.py +++ b/django_unicorn/views/objects.py @@ -5,6 +5,7 @@ from django_unicorn.components import HashUpdate, LocationUpdate, PollUpdate from django_unicorn.errors import UnicornViewError from django_unicorn.serializer import JSONDecodeError, dumps, loads +from django_unicorn.settings import get_setting from django_unicorn.utils import generate_checksum, is_int logger = logging.getLogger(__name__) @@ -88,6 +89,9 @@ def validate_checksum(self): """ Validates that the checksum in the request matches the data. + If `CHECK_CHECKSUM_MATCH` is set to False, + checking of matching checksum is avoided. + Returns: Raises `AssertionError` if the checksums don't match. """ @@ -97,11 +101,14 @@ def validate_checksum(self): # TODO: Raise specific exception raise AssertionError("Missing checksum") - generated_checksum = generate_checksum(self.data) + check_checksum_match = get_setting("CHECK_CHECKSUM_MATCH", True) - if checksum != generated_checksum: - # TODO: Raise specific exception - raise AssertionError("Checksum does not match") + if check_checksum_match: + generated_checksum = generate_checksum(self.data) + + if checksum != generated_checksum: + # TODO: Raise specific exception + raise AssertionError("Checksum does not match") class Return: diff --git a/pyproject.toml b/pyproject.toml index ac9d1cb7..113713c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,23 @@ [tool.poetry] -name = "django-unicorn" -version = "0.61.0" -description = "A magical full-stack framework for Django." -authors = ["Adam Hill "] +name = "django-unicorn-loosened" +version = "0.61.0.1" +description = "An alternate version of a magical full-stack framework for Django." +authors = ["Adam Hill ", "Styria Digital Development "] license = "MIT" readme = "README.md" -repository = "https://github.com/adamghill/django-unicorn/" +repository = "https://github.com/Styria-Digital/django-unicorn" homepage = "https://www.django-unicorn.com" documentation = "https://www.django-unicorn.com/docs/" keywords = ["django", "python", "javascript", "fullstack"] +packages = [ + { include = "django_unicorn" } +] [tool.poetry.urls] "Funding" = "https://github.com/sponsors/adamghill" [tool.poetry.dependencies] -python = ">=3.10" +python = ">=3.9" django = ">=2.2" beautifulsoup4 = ">=4.8.0" orjson = ">=3.6.0"