diff --git a/private/js/lib/01_modernizr.js b/private/js/lib/01_modernizr.js index 804a64c..f43167d 100644 --- a/private/js/lib/01_modernizr.js +++ b/private/js/lib/01_modernizr.js @@ -1,6 +1,6 @@ /*! - * modernizr v3.5.0 - * Build https://modernizr.com/download?-touchevents-setclasses-dontmin + * modernizr v3.6.0 + * Build https://modernizr.com/download?-cssanimations-touchevents-setclasses-dontmin * * Copyright (c) * Faruk Ates @@ -39,7 +39,7 @@ var ModernizrProto = { // The current version, dummy - _version: '3.5.0', + _version: '3.6.0', // Any settings that don't work as separate modules // can go in here as configuration. @@ -510,6 +510,464 @@ This test will also return `true` for Firefox 4 Multitouch support. }); + /** + * If the browsers follow the spec, then they would expose vendor-specific styles as: + * elem.style.WebkitBorderRadius + * instead of something like the following (which is technically incorrect): + * elem.style.webkitBorderRadius + + * WebKit ghosts their properties in lowercase but Opera & Moz do not. + * Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ + * erik.eae.net/archives/2008/03/10/21.48.10/ + + * More here: github.com/Modernizr/Modernizr/issues/issue/21 + * + * @access private + * @returns {string} The string representing the vendor-specific style properties + */ + + var omPrefixes = 'Moz O ms Webkit'; + + + var cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []); + ModernizrProto._cssomPrefixes = cssomPrefixes; + + + /** + * List of JavaScript DOM values used for tests + * + * @memberof Modernizr + * @name Modernizr._domPrefixes + * @optionName Modernizr._domPrefixes + * @optionProp domPrefixes + * @access public + * @example + * + * Modernizr._domPrefixes is exactly the same as [_prefixes](#modernizr-_prefixes), but rather + * than kebab-case properties, all properties are their Capitalized variant + * + * ```js + * Modernizr._domPrefixes === [ "Moz", "O", "ms", "Webkit" ]; + * ``` + */ + + var domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []); + ModernizrProto._domPrefixes = domPrefixes; + + + + /** + * contains checks to see if a string contains another string + * + * @access private + * @function contains + * @param {string} str - The string we want to check for substrings + * @param {string} substr - The substring we want to search the first string for + * @returns {boolean} + */ + + function contains(str, substr) { + return !!~('' + str).indexOf(substr); + } + + ; + + /** + * fnBind is a super small [bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) polyfill. + * + * @access private + * @function fnBind + * @param {function} fn - a function you want to change `this` reference to + * @param {object} that - the `this` you want to call the function with + * @returns {function} The wrapped version of the supplied function + */ + + function fnBind(fn, that) { + return function() { + return fn.apply(that, arguments); + }; + } + + ; + + /** + * testDOMProps is a generic DOM property test; if a browser supports + * a certain property, it won't return undefined for it. + * + * @access private + * @function testDOMProps + * @param {array.} props - An array of properties to test for + * @param {object} obj - An object or Element you want to use to test the parameters again + * @param {boolean|object} elem - An Element to bind the property lookup again. Use `false` to prevent the check + * @returns {false|*} returns false if the prop is unsupported, otherwise the value that is supported + */ + function testDOMProps(props, obj, elem) { + var item; + + for (var i in props) { + if (props[i] in obj) { + + // return the property name as a string + if (elem === false) { + return props[i]; + } + + item = obj[props[i]]; + + // let's bind a function + if (is(item, 'function')) { + // bind to obj unless overriden + return fnBind(item, elem || obj); + } + + // return the unbound function or obj or value + return item; + } + } + return false; + } + + ; + + /** + * cssToDOM takes a kebab-case string and converts it to camelCase + * e.g. box-sizing -> boxSizing + * + * @access private + * @function cssToDOM + * @param {string} name - String name of kebab-case prop we want to convert + * @returns {string} The camelCase version of the supplied name + */ + + function cssToDOM(name) { + return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) { + return m1 + m2.toUpperCase(); + }).replace(/^-/, ''); + } + ; + + /** + * Create our "modernizr" element that we do most feature tests on. + * + * @access private + */ + + var modElem = { + elem: createElement('modernizr') + }; + + // Clean up this element + Modernizr._q.push(function() { + delete modElem.elem; + }); + + + + var mStyle = { + style: modElem.elem.style + }; + + // kill ref for gc, must happen before mod.elem is removed, so we unshift on to + // the front of the queue. + Modernizr._q.unshift(function() { + delete mStyle.style; + }); + + + + /** + * domToCSS takes a camelCase string and converts it to kebab-case + * e.g. boxSizing -> box-sizing + * + * @access private + * @function domToCSS + * @param {string} name - String name of camelCase prop we want to convert + * @returns {string} The kebab-case version of the supplied name + */ + + function domToCSS(name) { + return name.replace(/([A-Z])/g, function(str, m1) { + return '-' + m1.toLowerCase(); + }).replace(/^ms-/, '-ms-'); + } + ; + + + /** + * wrapper around getComputedStyle, to fix issues with Firefox returning null when + * called inside of a hidden iframe + * + * @access private + * @function computedStyle + * @param {HTMLElement|SVGElement} - The element we want to find the computed styles of + * @param {string|null} [pseudoSelector]- An optional pseudo element selector (e.g. :before), of null if none + * @returns {CSSStyleDeclaration} + */ + + function computedStyle(elem, pseudo, prop) { + var result; + + if ('getComputedStyle' in window) { + result = getComputedStyle.call(window, elem, pseudo); + var console = window.console; + + if (result !== null) { + if (prop) { + result = result.getPropertyValue(prop); + } + } else { + if (console) { + var method = console.error ? 'error' : 'log'; + console[method].call(console, 'getComputedStyle returning null, its possible modernizr test results are inaccurate'); + } + } + } else { + result = !pseudo && elem.currentStyle && elem.currentStyle[prop]; + } + + return result; + } + + ; + + /** + * nativeTestProps allows for us to use native feature detection functionality if available. + * some prefixed form, or false, in the case of an unsupported rule + * + * @access private + * @function nativeTestProps + * @param {array} props - An array of property names + * @param {string} value - A string representing the value we want to check via @supports + * @returns {boolean|undefined} A boolean when @supports exists, undefined otherwise + */ + + // Accepts a list of property names and a single value + // Returns `undefined` if native detection not available + function nativeTestProps(props, value) { + var i = props.length; + // Start with the JS API: http://www.w3.org/TR/css3-conditional/#the-css-interface + if ('CSS' in window && 'supports' in window.CSS) { + // Try every prefixed variant of the property + while (i--) { + if (window.CSS.supports(domToCSS(props[i]), value)) { + return true; + } + } + return false; + } + // Otherwise fall back to at-rule (for Opera 12.x) + else if ('CSSSupportsRule' in window) { + // Build a condition string for every prefixed variant + var conditionText = []; + while (i--) { + conditionText.push('(' + domToCSS(props[i]) + ':' + value + ')'); + } + conditionText = conditionText.join(' or '); + return injectElementWithStyles('@supports (' + conditionText + ') { #modernizr { position: absolute; } }', function(node) { + return computedStyle(node, null, 'position') == 'absolute'; + }); + } + return undefined; + } + ; + + // testProps is a generic CSS / DOM property test. + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + // Property names can be provided in either camelCase or kebab-case. + + function testProps(props, prefixed, value, skipValueTest) { + skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest; + + // Try native detect first + if (!is(value, 'undefined')) { + var result = nativeTestProps(props, value); + if (!is(result, 'undefined')) { + return result; + } + } + + // Otherwise do it properly + var afterInit, i, propsLength, prop, before; + + // If we don't have a style element, that means we're running async or after + // the core tests, so we'll need to create our own elements to use + + // inside of an SVG element, in certain browsers, the `style` element is only + // defined for valid tags. Therefore, if `modernizr` does not have one, we + // fall back to a less used element and hope for the best. + // for strict XHTML browsers the hardly used samp element is used + var elems = ['modernizr', 'tspan', 'samp']; + while (!mStyle.style && elems.length) { + afterInit = true; + mStyle.modElem = createElement(elems.shift()); + mStyle.style = mStyle.modElem.style; + } + + // Delete the objects if we created them. + function cleanElems() { + if (afterInit) { + delete mStyle.style; + delete mStyle.modElem; + } + } + + propsLength = props.length; + for (i = 0; i < propsLength; i++) { + prop = props[i]; + before = mStyle.style[prop]; + + if (contains(prop, '-')) { + prop = cssToDOM(prop); + } + + if (mStyle.style[prop] !== undefined) { + + // If value to test has been passed in, do a set-and-check test. + // 0 (integer) is a valid property value, so check that `value` isn't + // undefined, rather than just checking it's truthy. + if (!skipValueTest && !is(value, 'undefined')) { + + // Needs a try catch block because of old IE. This is slow, but will + // be avoided in most cases because `skipValueTest` will be used. + try { + mStyle.style[prop] = value; + } catch (e) {} + + // If the property value has changed, we assume the value used is + // supported. If `value` is empty string, it'll fail here (because + // it hasn't changed), which matches how browsers have implemented + // CSS.supports() + if (mStyle.style[prop] != before) { + cleanElems(); + return prefixed == 'pfx' ? prop : true; + } + } + // Otherwise just return true, or the property name if this is a + // `prefixed()` call + else { + cleanElems(); + return prefixed == 'pfx' ? prop : true; + } + } + } + cleanElems(); + return false; + } + + ; + + /** + * testPropsAll tests a list of DOM properties we want to check against. + * We specify literally ALL possible (known and/or likely) properties on + * the element including the non-vendor prefixed one, for forward- + * compatibility. + * + * @access private + * @function testPropsAll + * @param {string} prop - A string of the property to test for + * @param {string|object} [prefixed] - An object to check the prefixed properties on. Use a string to skip + * @param {HTMLElement|SVGElement} [elem] - An element used to test the property and value against + * @param {string} [value] - A string of a css value + * @param {boolean} [skipValueTest] - An boolean representing if you want to test if value sticks when set + * @returns {false|string} returns the string version of the property, or false if it is unsupported + */ + function testPropsAll(prop, prefixed, elem, value, skipValueTest) { + + var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), + props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); + + // did they call .prefixed('boxSizing') or are we just testing a prop? + if (is(prefixed, 'string') || is(prefixed, 'undefined')) { + return testProps(props, prefixed, value, skipValueTest); + + // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) + } else { + props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); + return testDOMProps(props, prefixed, elem); + } + } + + // Modernizr.testAllProps() investigates whether a given style property, + // or any of its vendor-prefixed variants, is recognized + // + // Note that the property names must be provided in the camelCase variant. + // Modernizr.testAllProps('boxSizing') + ModernizrProto.testAllProps = testPropsAll; + + + + /** + * testAllProps determines whether a given CSS property is supported in the browser + * + * @memberof Modernizr + * @name Modernizr.testAllProps + * @optionName Modernizr.testAllProps() + * @optionProp testAllProps + * @access public + * @function testAllProps + * @param {string} prop - String naming the property to test (either camelCase or kebab-case) + * @param {string} [value] - String of the value to test + * @param {boolean} [skipValueTest=false] - Whether to skip testing that the value is supported when using non-native detection + * @example + * + * testAllProps determines whether a given CSS property, in some prefixed form, + * is supported by the browser. + * + * ```js + * testAllProps('boxSizing') // true + * ``` + * + * It can optionally be given a CSS value in string form to test if a property + * value is valid + * + * ```js + * testAllProps('display', 'block') // true + * testAllProps('display', 'penguin') // false + * ``` + * + * A boolean can be passed as a third parameter to skip the value check when + * native detection (@supports) isn't available. + * + * ```js + * testAllProps('shapeOutside', 'content-box', true); + * ``` + */ + + function testAllProps(prop, value, skipValueTest) { + return testPropsAll(prop, undefined, undefined, value, skipValueTest); + } + ModernizrProto.testAllProps = testAllProps; + +/*! +{ + "name": "CSS Animations", + "property": "cssanimations", + "caniuse": "css-animation", + "polyfills": ["transformie", "csssandpaper"], + "tags": ["css"], + "warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"], + "notes": [{ + "name" : "Article: 'Dispelling the Android CSS animation myths'", + "href": "https://goo.gl/OGw5Gm" + }] +} +!*/ +/* DOC +Detects whether or not elements can be animated using CSS +*/ + + Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true)); + + // Run each test testRunner(); diff --git a/private/js/modules/__util.js b/private/js/modules/__util.js index 071a21a..14f7e19 100644 --- a/private/js/modules/__util.js +++ b/private/js/modules/__util.js @@ -1,5 +1,13 @@ window.transitionend = 'webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend'; +window.on_transitionend = function($element, do_function) { + if (Modernizr.csstransitions) { + $element.on(window.transitionend, do_function); + } else { + do_function(false) + } +}; + $(function() { var $body = $('body'); diff --git a/private/js/modules/_reveal.js b/private/js/modules/_reveal.js index f1582e7..9664999 100644 --- a/private/js/modules/_reveal.js +++ b/private/js/modules/_reveal.js @@ -29,7 +29,7 @@ $(function() { create_reveal_elements($('html')); function remove_reveal_animation(event) { - if (event.target === this) { + if (!event || event.target === this) { $(this).removeClass('reveal_animation'); $(this).off(window.transitionend, remove_reveal_animation); } @@ -43,7 +43,7 @@ $(function() { function reveal_element(element) { window.requestAnimationFrame(function() { - $(element).on(window.transitionend, remove_reveal_animation); + window.on_transitionend($(element), remove_reveal_animation); $(element).removeClass('reveal'); }); } diff --git a/private/js/modules/control-panel.js b/private/js/modules/control-panel.js index 994e7f6..47027ef 100644 --- a/private/js/modules/control-panel.js +++ b/private/js/modules/control-panel.js @@ -55,17 +55,13 @@ $(function() { $body.on('submit', '.control__item form', function(event) { event.preventDefault(); var $form = $(this); + $form.addClass('loading'); $.ajax({ type: $form.attr('method'), url: $form.attr('action'), data: $form.serialize(), success: function(data) { - if ($(data).hasClass('control__item__success')) { - var $control_item = $form.parents('.control__item'); - $control_item.find('.control__item__close').trigger('click'); - } else { - $form.replaceWith(data); - } + $form.replaceWith(data); } }); }); @@ -76,7 +72,7 @@ $(function() { window.location = '#form'; } - $task_form.formset(); + // $task_form.formset(); $task_form.on('formAdded', function(event) { var $title = $(event.target).find('h3'); var id = parseInt($title.attr('data-id').match(/\d+/)[0]); @@ -86,4 +82,49 @@ $(function() { $task_form.on('formDeleted', function(event) { $(event.target).hide(); }); + + /* DOWNLOADS */ + + var download_texts = []; + + $('.download__item__title').each(function() { + var text = $(this).text().toLowerCase(); + text = text + ' ' + $(this).next().text().toLowerCase(); + text = text + ' ' + $(this).next().next().text().toLowerCase(); + download_texts.push({ + $element: $(this).parents('li'), + text: text + }); + }); + + $body.on('input', '#downloads_search', function(event) { + var query = $(this).val().toLowerCase(); + var query_list = $.trim(query).split(' '); + var matches = []; + + for (var i = 0; i < download_texts.length; i++) { + var download_text_item = download_texts[i]; + var matched = false; + if (!matched) { + for (var ii = 0; ii < query_list.length; ii++) { + var query_item = query_list[ii]; + if (download_text_item.text.indexOf(query_item) >= 0) { + matched = true; + matches.push(download_text_item.$element); + } + } + } + } + + $('#downloads li').each(function() { + $(this).parents('.downloads__section').css('display', 'none'); + $(this).css('display', 'none'); + }); + for (i = 0; i < matches.length; i++) { + matches[i].removeAttr('style'); + matches[i].parents('.downloads__section').removeAttr('style'); + } + }); + + $('#downloads_search').trigger('input'); }); \ No newline at end of file diff --git a/private/js/modules/downloads.js b/private/js/modules/downloads.js deleted file mode 100644 index d5d04be..0000000 --- a/private/js/modules/downloads.js +++ /dev/null @@ -1,47 +0,0 @@ -$(function() { - 'use strict'; - - var $body = $('body'); - - var download_texts = []; - - $('.downloads__item__text').each(function() { - var text = $(this).text().toLowerCase(); - text = text + '' + $(this).next().text().toLowerCase(); - download_texts.push({ - $element: $(this).parents('.downloads__item__frame'), - text: text - }); - }); - - $body.on('input', '#downloads_search', function(event) { - var query = $(this).val().toLowerCase(); - var query_list = $.trim(query).split(' '); - var matches = []; - - for (var i = 0; i < download_texts.length; i++) { - var download_text_item = download_texts[i]; - var matched = false; - if (!matched) { - for (var ii = 0; ii < query_list.length; ii++) { - var query_item = query_list[ii]; - if (download_text_item.text.indexOf(query_item) >= 0) { - matched = true; - matches.push(download_text_item.$element); - } - } - } - } - - $('.downloads__item__frame').each(function() { - $(this).parents('.downloads__section').css('display', 'none'); - $(this).css('display', 'none'); - }); - for (i = 0; i < matches.length; i++) { - matches[i].removeAttr('style'); - matches[i].parents('.downloads__section').removeAttr('style'); - } - }); - - $('#downloads_search').trigger('input'); -}); \ No newline at end of file diff --git a/private/js/modules/list.js b/private/js/modules/list.js new file mode 100644 index 0000000..e62fbc4 --- /dev/null +++ b/private/js/modules/list.js @@ -0,0 +1,53 @@ +var search = window.location.search; +if (search.indexOf('?page=1') === -1 && (search.indexOf('?page=') >= 0 || search.indexOf('&page=') >= 0)) { + document.getElementsByTagName("body")[0].classList.add('hidden'); + window.location = window.location.pathname; +} + +$(function() { + 'use strict'; + + var $body = $('body'); + + $body.on('click', '.list__button', function(event) { + event.preventDefault(); + var $button = $(this); + var $load_frame = $button.parents('.load__frame'); + + $button.width($button.width()); + window.requestAnimationFrame(function() { + $load_frame.addClass('loading'); + $load_frame.height($load_frame.outerHeight()); + var load_frame_id = $load_frame.attr('id'); + var $load_replace = $button.parents('.load__replace'); + $.get($button.attr('href'), function(data) { + var loaded_data = $(data).find('#' + load_frame_id + ' .load__main').html(); + $load_frame.addClass('loaded'); + window.setTimeout(function() { + $load_replace.replaceWith(loaded_data); + window.on_transitionend($load_frame, function(event) { + if (!event || event.target === this) { + $load_frame.removeAttr('style'); + $load_frame.removeClass('loading loaded'); + $load_frame.off(window.transitionend) + } + }); + $load_frame.height($load_frame.find('.load__main').height()); + window.create_reveal_elements($load_frame); + init_auto_load(); + }, 200); + }); + }); + }); + + function init_auto_load() { + $('.load__replace--auto').each(function() { + var that = this; + var elementWatcher = scrollMonitor.create(that); + elementWatcher.enterViewport(function() { + $(that).find('a').trigger('click'); + }); + }); + } + init_auto_load(); +}); \ No newline at end of file diff --git a/private/js/modules/navigation.js b/private/js/modules/navigation.js index 81b7c40..8731184 100644 --- a/private/js/modules/navigation.js +++ b/private/js/modules/navigation.js @@ -13,7 +13,8 @@ $(function() { var href = $(this).attr('href'); var target = $(this).attr('target'); var is_event = $(this).hasClass('event'); - if (href.indexOf('/') === 0 && !target && !is_event && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey && !$('html').hasClass('cms-ready')) { + var is_load = $(this).hasClass('button--load'); + if (href.indexOf('/') === 0 && !is_load && !target && !is_event && !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey && !$('html').hasClass('cms-ready')) { event.preventDefault(); $body.addClass('unload loading'); window.setTimeout(function() { @@ -86,7 +87,7 @@ $(function() { window.navigation_is_open = true; $body.removeClass('notification_open'); - $navigation.one(window.transitionend, function() { + window.on_transitionend($navigation, function() { window.requestAnimationFrame(function() { $navigation.css('position', 'relative'); $navigation.off(window.transitionend); diff --git a/private/scss/_config.scss b/private/scss/_config.scss index b563aa8..dee7afa 100644 --- a/private/scss/_config.scss +++ b/private/scss/_config.scss @@ -4,6 +4,7 @@ $light_gray: #F4F4F4; $medium_light_gray: #E6E6E6; $gray: #ADADAD; $dark_gray: #8f8f8f; +$black_gray: #737373; $near_black: #444444; $black: #000000; diff --git a/private/scss/_typography.scss b/private/scss/_typography.scss index b3708e1..73fb4b1 100644 --- a/private/scss/_typography.scss +++ b/private/scss/_typography.scss @@ -39,4 +39,12 @@ h2, .h2 { @media screen and (max-width: $medium_breakpoint) { font-size: em(30px); } +} + +h3, .h3 { + font-size: em(18px); + color: $dark_gray; + font-weight: 700; + line-height: 1.45; + letter-spacing: 0.01em; } \ No newline at end of file diff --git a/private/scss/main.scss b/private/scss/main.scss index 579f000..25a1b74 100644 --- a/private/scss/main.scss +++ b/private/scss/main.scss @@ -13,7 +13,6 @@ @import "modules/_contact.scss"; @import "modules/_content.scss"; @import "modules/_admin_editor.scss"; -@import "modules/_downloads.scss"; @import "modules/_control-panel.scss"; @import "modules/plugins/_quote.scss"; @import "modules/plugins/_slider.scss"; diff --git a/private/scss/modules/_content.scss b/private/scss/modules/_content.scss index cbf7b11..171e078 100644 --- a/private/scss/modules/_content.scss +++ b/private/scss/modules/_content.scss @@ -7,6 +7,27 @@ $max-width: 100%; padding-top: em(50px); } +.content__frame--dialog { + display: table; + width: 100%; + height: 100%; + .content__container { + display: table-cell; + width: 100%; + height: 100%; + vertical-align: middle; + text-align: center; + } +} + +.content__dialog { + display: inline-block; + width: 100%; + max-width: em(600px); + padding: em(100px) em(30px); + text-align: left; +} + .content__intro { width: 100%; height: 70vh; @@ -17,14 +38,6 @@ $max-width: 100%; padding: em(50px) 10% 0; background: $light_gray; transition: background 0.5s $easeOutQuart; - h1 { - width: 100%; - z-index: 3; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%) translateY(em(40px)); - } &.reveal { transform: none; } @@ -48,8 +61,34 @@ $max-width: 100%; } @media screen and (max-width: $medium_breakpoint) { height: 50vh; - h1 { - transform: translateY(-50%) translateY(em(25px)); + } +} + +.content__intro__content { + width: 100%; + z-index: 3; + position: absolute; + left: 0; + top: 50%; + text-align: center; + color: $white; + padding: 0 em(30px); + p { + line-height: 1.3; + font-size: em(18px); + margin: em(20px) 0 0; + } + a { + margin: em(40px) em(10px) 0; + } + transform: translateY(-50%) translateY(em(40px)); + &.reveal { + transform: translateX(100px) translateY(-50%) translateY(em(40px)); + } + @media screen and (max-width: $medium_breakpoint) { + transform: translateY(-50%) translateY(em(25px)); + &.reveal { + transform: translateX(100px) translateY(-50%) translateY(em(25px)); } } } diff --git a/private/scss/modules/_control-panel.scss b/private/scss/modules/_control-panel.scss index cc0ea32..3a5f6bd 100644 --- a/private/scss/modules/_control-panel.scss +++ b/private/scss/modules/_control-panel.scss @@ -1,17 +1,12 @@ .control_panel { width: 100%; - padding: em(100px) 0; + padding: em(100px) 0 0; will-change: transform, height; - &.reveal_animation { - transition: background $reveal_duration $reveal_timing_function; - &.reveal { - background: transparent; - } - } - .load__replace { - margin-top: em(40px); - text-align: left; - } + text-align: left; +} + +.task__form { + margin-top: em(50px); } .control__list { @@ -21,26 +16,27 @@ } .control_panel__content { - max-width: em(1440px); - margin: 0 auto; - padding: 0 em(80px); font-size: 0; - @media screen and (max-width: $large_breakpoint) { - padding: 0 em(60px); - } - @media screen and (max-width: $medium_breakpoint) { - max-width: em(600px); - padding: 0 em(30px); - } } .control__item { display: block; background: $white_gray; + color: $black_gray; text-decoration: none; width: 100%; margin-bottom: em(15px); position: relative; + line-height: 1.3; + border-bottom: 3px solid $white_gray; + &.loading { + &:before { + top: 50%; + width: em(30px); + height: em(30px); + margin-top: em(-15px); + } + } } .control__item__open { @@ -76,10 +72,8 @@ .control__item__title { display: block; width: 100%; - padding: em(22px) em(30px); + padding: em(23px) em(30px) em(20px); position: relative; - font-size: em(18px); - color: $dark_gray; .control__item--button & { padding-right: em(240px); .button { @@ -100,10 +94,10 @@ } } .control__item--status & { - padding-left: em(85px); + padding-left: em(80px); } .control__item--arrow & { - padding-right: em(85px); + padding-right: em(80px); &:hover { .control__item__arrow { transform: translateX(em(5px)); @@ -123,8 +117,10 @@ } .control__item__content__main { - border-top: 1px solid $gray; + border: 3px solid $white_gray; + border-bottom: 0; padding: em(30px); + background: $white; } .control__item__fields { @@ -135,23 +131,24 @@ display: inline-block; position: absolute; top: 50%; - left: em(20px); - margin-top: em(-20px); - border-radius: 50%; + left: em(22px); + width: em(34px); + height: em(34px); + margin-top: em(-16px); border: 2px solid $gray; svg { - display: block; - width: em(36px); - height: em(36px); + display: none; + width: 60%; + height: 60%; + margin: 20%; fill: $white; } .control__item--status--active & { background: $green; border-color: $green; - } - .control__item--status--expired & { - background: $red; - border-color: $red; + svg { + display: block; + } } } @@ -160,36 +157,95 @@ position: absolute; top: 50%; right: em(20px); - margin-top: em(-20px); + margin-top: em(-13px); transform: none; transition: transform 0.3s $easeOutQuart; svg { display: block; - width: em(40px); - height: em(40px); - transform: rotate(-90deg); - } - .fill { + width: em(26px); + height: em(26px); fill: $green; } } -.todo { +.control_panel__content__item { display: inline-block; vertical-align: top; width: 48%; - margin-right: 4%; + font-size: em(18px); + position: relative; + &:first-child { + margin-right: 4%; + } @media screen and (max-width: $large_breakpoint) { + display: block; width: 100%; - margin: 0 0 em(40px) 0; + margin: 0 0 em(50px) 0; } } -.settings { - display: inline-block; - vertical-align: top; - width: 48%; +.downloads__frame { + width: 100%; + padding: em(150px) 0; + position: relative; + will-change: transform; + .downloads { + li { + width: 33.3333%; + @media screen and (max-width: $large_breakpoint) { + width: 50%; + } + @media screen and (max-width: $medium_breakpoint) { + width: 100%; + } + } + } @media screen and (max-width: $large_breakpoint) { - width: 100%; + padding-top: 0; + } +} + +.downloads__section { + margin-top: em(80px); + .downloads--flat & { + margin-top: 0; } -} \ No newline at end of file +} + +.downloads__section__title { + width: 100%; + overflow: hidden; + h3 { + display: inline-block; + position: relative; + &:after { + content: ''; + display: block; + position: absolute; + background: $medium_light_gray; + width: 100vw; + height: em(3px); + top: em(15px); + left: 100%; + margin-left: em(20px); + } + } +} + +.downloads__section__title--flat { + h3 { + &:after { + display: none; + } + } +} + +.downloads__list { + display: block; + margin: 0 em(-10px); + font-size: 0; +} + +.downloads__item__tags { + display: none; +} diff --git a/private/scss/modules/_downloads.scss b/private/scss/modules/_downloads.scss deleted file mode 100644 index 3d69af8..0000000 --- a/private/scss/modules/_downloads.scss +++ /dev/null @@ -1,66 +0,0 @@ -.downloads__frame { - width: 100%; - padding: em(80px) 0 em(150px); - position: relative; - will-change: transform; - .downloads { - li { - width: 33.3333%; - } - } -} - -.downloads__content { - max-width: em(1440px); - margin: 0 auto; - padding: 0 em(80px); - @media screen and (max-width: $large_breakpoint) { - padding: 0 em(60px); - } - @media screen and (max-width: $medium_breakpoint) { - max-width: em(600px); - padding: 0 em(30px); - } -} - -.downloads__section { - margin-top: em(80px); - .downloads--flat & { - margin-top: 0; - } -} - -.downloads__section__title { - width: 100%; - overflow: hidden; - h3 { - display: inline-block; - position: relative; - &:after { - content: ''; - display: block; - position: absolute; - background: $white; - width: 100vw; - height: em(3px); - opacity: 0.15; - top: em(10.5px); - left: 100%; - margin-left: em(20px); - } - } -} - -.downloads__section__title--flat { - h3 { - &:after { - display: none; - } - } -} - -.downloads__list { - display: block; - margin: 0 em(-10px); - font-size: 0; -} diff --git a/private/scss/modules/_footer.scss b/private/scss/modules/_footer.scss index 9686f99..5811e77 100644 --- a/private/scss/modules/_footer.scss +++ b/private/scss/modules/_footer.scss @@ -57,8 +57,8 @@ @media screen and (min-width: $medium_breakpoint + 1) { padding: 0 em(30px) !important; .header__button__icon { - width: em(16px); - height: em(16px); + width: em(12px); + height: em(12px); top: em(-1px); margin-right: em(10px); } diff --git a/private/scss/modules/plugins/_download_section.scss b/private/scss/modules/plugins/_download_section.scss index 014520d..ff8b334 100644 --- a/private/scss/modules/plugins/_download_section.scss +++ b/private/scss/modules/plugins/_download_section.scss @@ -68,7 +68,8 @@ color: $white; font-weight: 500; padding: 0 em(5px); - text-shadow: 0 1px 0 $green, 1px 0 0 $green, 0 -1px 0 $green, -1px 0 0 $green; + text-shadow: 1px 0 0 $green, 0 1px 0 $green, -1px 0 0 $green, 0 -1px 0 $green, + 2px 0 0 $green, 0 2px 0 $green, -2px 0 0 $green, 0 -2px 0 $green; &:before, &:after { display: block; content: ''; diff --git a/private/scss/modules/plugins/_form.scss b/private/scss/modules/plugins/_form.scss index 6b5aede..72dc595 100644 --- a/private/scss/modules/plugins/_form.scss +++ b/private/scss/modules/plugins/_form.scss @@ -44,6 +44,10 @@ .form__submit { text-align: right; + .button { + vertical-align: top; + margin-left: em(10px); + } } .button { @@ -76,6 +80,31 @@ } } +.button--small { + height: em(35px); + line-height: em(35px); + font-size: em(14px); +} + +.button--ghost { + background: none; + color: $green; + svg { + fill: $green !important; + } + &:after { + content: ''; + position: absolute; + z-index: -1; + display: block; + top: 0; + right: 0; + left: 0; + bottom: 0; + border: 2px solid $green; + } +} + .button__icon { display: block; position: absolute; @@ -90,6 +119,9 @@ width: em(36px); fill: $white; margin: em(12px) 0; + .button--small & { + margin: em(7px) 0; + } } } @@ -104,6 +136,78 @@ width: 100%; display: block; margin-bottom: em(15px); + position: relative; +} + +.form__field--label { + padding-left: 30%; + label { + display: block; + position: absolute; + top: 0; + left: 0; + width: 30%; + line-height: em(40px); + font-weight: 400; + padding-right: em(20px); + font-size: em(12px); + letter-spacing: 0.03em; + text-transform: uppercase; + span { + line-height: 1.3; + display: inline-block; + vertical-align: middle; + } + } + @media screen and (max-width: $small_breakpoint) { + padding: 0; + label { + position: relative; + width: 100%; + padding: 0; + margin-bottom: em(2px); + } + } +} + +.form__field--icon { + input { + height: em(70px); + padding: 0 em(90px) 0 em(15px); + } + button, a { + display: block; + -webkit-appearance: none; + position: absolute; + top: 0; + right: 0; + width: em(70px); + height: em(70px); + line-height: em(65px); + border: none; + font-size: 0; + text-align: center; + padding: em(3px); + border-top-right-radius: em(5px); + border-bottom-right-radius: em(5px); + background: transparent; + color: inherit; + cursor: pointer; + svg { + vertical-align: middle; + width: em(36px); + height: em(36px); + display: inline-block; + transform: none; + fill: $green; + transition: transform 0.3s $easeOutQuart; + } + &:hover { + svg { + transform: scale(1.1); + } + } + } } input, textarea { @@ -161,7 +265,7 @@ textarea { .form__success, .form__errors, .form__field__errors { display: block; width: 100%; - font-size: em(16px); + font-size: em(14px); font-weight: 700; line-height: 1.5; } diff --git a/private/scss/modules/plugins/_slider.scss b/private/scss/modules/plugins/_slider.scss index 613091b..8f22f35 100644 --- a/private/scss/modules/plugins/_slider.scss +++ b/private/scss/modules/plugins/_slider.scss @@ -233,6 +233,7 @@ font-weight: 700; margin-bottom: em(5px); line-height: 1.3; + color: $white; } .slider__text__item__subline { diff --git a/requirements.in b/requirements.in index 4a006d2..94ac202 100644 --- a/requirements.in +++ b/requirements.in @@ -10,7 +10,6 @@ https://control.divio.com/api/v1/apps/serve/djangocms-picture/2.0.6/005e8663-d1c https://control.divio.com/api/v1/apps/serve/djangocms-text-ckeditor/3.5.3/a7b5179f-cea5-4af8-b235-6b7f709c4e6a/djangocms-text-ckeditor-3.5.3.tar.gz#egg=djangocms-text-ckeditor==3.5.3 https://control.divio.com/api/v1/apps/serve/django-filer/1.3.0.1/bcb7d25b-6922-48a9-a252-9bc165f6403e/django-filer-1.3.0.1.tar.gz#egg=django-filer==1.3.0.1 # -lxml Whoosh==2.7.4 aldryn-search==0.5.0 django-fontawesome==0.3.1 diff --git a/settings.py b/settings.py index ee6bea6..eec11ff 100644 --- a/settings.py +++ b/settings.py @@ -33,6 +33,9 @@ INSTALLED_APPS.extend([ 'image_cropping', ]) + +LOGIN_REDIRECT_URL = 'memberzone:overview' + MIDDLEWARE_CLASSES.extend([ # add your own middlewares here ]) diff --git a/src/memberzone/admin.py b/src/memberzone/admin.py index 9f19583..fc1a392 100644 --- a/src/memberzone/admin.py +++ b/src/memberzone/admin.py @@ -1,19 +1,11 @@ -import xlwt -import json - from aldryn_forms.utils import get_user_model -from django.conf.urls import url -from django.contrib.admin.utils import unquote -from django.http import HttpResponse -from django.utils.text import slugify from django.contrib import admin from django.utils.translation import ugettext_lazy as _ - -from memberzone.models import MemberTask, MemberTaskFormField, MemberTaskRegistration, MemberDownloadFile, \ - MemberDownloadTag, Profile, MemberDownloadSection - +from parler.admin import TranslatableAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from memberzone.models import Profile, MemberTask, MemberDownloadSection, MemberDownloadTag, MemberDownloadFile + User = get_user_model() @@ -34,86 +26,33 @@ admin.site.unregister(User) admin.site.register(User, UserAdmin) -class MemberTaskFormFieldInlineAdmin(admin.TabularInline): - model = MemberTaskFormField - extra = 0 - prepopulated_fields = { - 'name': ('label',) - } - - @admin.register(MemberTask) -class MemberTaskAdmin(admin.ModelAdmin): - list_display = ('title', 'publish_date', 'deadline') +class MemberTaskAdmin(TranslatableAdmin): + list_display = ('title', 'published', 'publish_date') list_filter = ('groups',) filter_horizontal = ('groups',) - prepopulated_fields = { - 'slug': ('title',) - } - inlines = [MemberTaskFormFieldInlineAdmin, ] - - def get_urls(self): - info = self.model._meta.app_label, self.model._meta.model_name - return [ - url( - r'^(.+)/export/$', - self.admin_site.admin_view(self.participants_export_view), - name='%s_%s_participants_export' % info, - ), - ] + super(MemberTaskAdmin, self).get_urls() - - def participants_export_view(self, request, object_id, *args, **kwargs): - obj = self.get_object(request, unquote(object_id)) - wb = xlwt.Workbook() - ws = wb.add_sheet('Teilnehmerliste') - r = 0 - c = 0 - fields = [] - for field in obj.fields.all(): - ws.write(r, c, field.label) - fields.append(field.name.replace('-', '_')) - c += 1 - for participant in obj.registrations.all(): - for data_set in json.loads(participant.form_data): - r += 1 - c = 0 - for field in fields: - value = data_set.get(field, '') - if isinstance(value, list): - value = ', '.join(value) - ws.write(r, c, value) - c += 1 - - response = HttpResponse(content_type="application/ms-excel") - response['Content-Disposition'] = 'attachment; filename=%s-teilnehmer.xls' % slugify(obj.title) - wb.save(response) - return response - - -@admin.register(MemberTaskRegistration) -class MemberTaskRegistrationAdmin(admin.ModelAdmin): - readonly_fields = ['task', 'user', 'form_data', 'cdate'] - - def has_delete_permission(self, request, obj=None): - return False - - def has_add_permission(self, request): - return False + readonly_fields = ('informed_users',) + fieldsets = ( + (None, {'fields': ('title', 'published', 'image', 'cropping')}), + (_('Permissions'), {'fields': ('groups',)}), + (_('Veröffentlichung'), {'fields': ('publish_date',)}), + (_('Information'), {'fields': ('informed_users',)}), + ) @admin.register(MemberDownloadSection) -class MemberDownloadSectionAdmin(admin.ModelAdmin): +class MemberDownloadSectionAdmin(TranslatableAdmin): list_display = ('title', 'ordering') list_editable = ['ordering'] @admin.register(MemberDownloadTag) -class MemberDownloadTagAdmin(admin.ModelAdmin): +class MemberDownloadTagAdmin(TranslatableAdmin): pass @admin.register(MemberDownloadFile) -class MemberDownloadFileAdmin(admin.ModelAdmin): +class MemberDownloadFileAdmin(TranslatableAdmin): list_display = ('label', 'ordering') list_editable = ['ordering'] list_filter = ('section', 'groups') diff --git a/src/memberzone/forms.py b/src/memberzone/forms.py index 752a23b..f5c72ba 100644 --- a/src/memberzone/forms.py +++ b/src/memberzone/forms.py @@ -1,8 +1,21 @@ +from django import forms from django.contrib.auth.forms import AuthenticationForm +from memberzone.models import Profile + class LoginForm(AuthenticationForm): def __init__(self, *args, **kwargs): super(LoginForm, self).__init__(*args, **kwargs) for _, value in self.fields.items(): value.widget.attrs['placeholder'] = value.label + + +class ProfileEditForm(forms.ModelForm): + class Meta: + model = Profile + fields = ['first_name', 'last_name', 'street', 'zip', 'place', 'email'] + + def __init__(self, *args, **kwargs): + super(ProfileEditForm, self).__init__(*args, **kwargs) + self.fields['zip'].widget = forms.TextInput() \ No newline at end of file diff --git a/src/memberzone/migrations/0003_auto_20180322_1052.py b/src/memberzone/migrations/0003_auto_20180322_1052.py new file mode 100644 index 0000000..e087925 --- /dev/null +++ b/src/memberzone/migrations/0003_auto_20180322_1052.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-03-22 10:52 +from __future__ import unicode_literals + +import cms.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import image_cropping.fields +import project.utils + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0018_pagenode'), + ('memberzone', '0002_auto_20180321_1256'), + ] + + operations = [ + migrations.CreateModel( + name='MemberDownloadFileTranslation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')), + ('name', models.CharField(blank=True, max_length=512, null=True, verbose_name='Name')), + ('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')), + ('file', models.FileField(max_length=512, upload_to='protected_files')), + ], + options={ + 'db_table': 'memberzone_memberdownloadfile_translation', + 'default_permissions': (), + 'managed': True, + 'db_tablespace': '', + 'verbose_name': 'Mitglieder Download Translation', + }, + ), + migrations.CreateModel( + name='MemberDownloadSectionTranslation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')), + ('title', models.CharField(max_length=100, verbose_name='Title')), + ], + options={ + 'db_table': 'memberzone_memberdownloadsection_translation', + 'default_permissions': (), + 'managed': True, + 'db_tablespace': '', + 'verbose_name': 'Mitglieder Download Section Translation', + }, + ), + migrations.CreateModel( + name='MemberDownloadTagTranslation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')), + ('name', models.CharField(max_length=100, verbose_name='Name')), + ], + options={ + 'db_table': 'memberzone_memberdownloadtag_translation', + 'default_permissions': (), + 'managed': True, + 'db_tablespace': '', + 'verbose_name': 'Mitglieder Download Tag Translation', + }, + ), + migrations.CreateModel( + name='MemberTaskTranslation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('language_code', models.CharField(db_index=True, max_length=15, verbose_name='Language')), + ('title', models.CharField(max_length=100, verbose_name='Title')), + ], + options={ + 'db_table': 'memberzone_membertask_translation', + 'default_permissions': (), + 'managed': True, + 'db_tablespace': '', + 'verbose_name': 'Mitglieder Aufgabe Translation', + }, + ), + migrations.AlterUniqueTogether( + name='membertaskformfield', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='membertaskformfield', + name='task', + ), + migrations.AlterUniqueTogether( + name='membertaskregistration', + unique_together=set([]), + ), + migrations.RemoveField( + model_name='membertaskregistration', + name='task', + ), + migrations.RemoveField( + model_name='membertaskregistration', + name='user', + ), + migrations.AlterModelOptions( + name='memberdownloadfile', + options={'ordering': ['ordering'], 'verbose_name': 'Mitglieder Download', 'verbose_name_plural': 'Mitglieder Downloads'}, + ), + migrations.RemoveField( + model_name='memberdownloadfile', + name='description', + ), + migrations.RemoveField( + model_name='memberdownloadfile', + name='file', + ), + migrations.RemoveField( + model_name='memberdownloadfile', + name='name', + ), + migrations.RemoveField( + model_name='memberdownloadsection', + name='title', + ), + migrations.RemoveField( + model_name='memberdownloadtag', + name='name', + ), + migrations.RemoveField( + model_name='membertask', + name='bodytext', + ), + migrations.RemoveField( + model_name='membertask', + name='deadline', + ), + migrations.RemoveField( + model_name='membertask', + name='folder', + ), + migrations.RemoveField( + model_name='membertask', + name='form_bodytext', + ), + migrations.RemoveField( + model_name='membertask', + name='max_num', + ), + migrations.RemoveField( + model_name='membertask', + name='slug', + ), + migrations.RemoveField( + model_name='membertask', + name='sub_title', + ), + migrations.RemoveField( + model_name='membertask', + name='submit_text', + ), + migrations.RemoveField( + model_name='membertask', + name='success_bodytext', + ), + migrations.RemoveField( + model_name='membertask', + name='success_title', + ), + migrations.RemoveField( + model_name='membertask', + name='title', + ), + migrations.AddField( + model_name='membertask', + name='cropping', + field=image_cropping.fields.ImageRatioField('image', '1200x800', adapt_rotation=False, allow_fullsize=False, free_crop=True, help_text=None, hide_image_field=False, size_warning=False, verbose_name='cropping'), + ), + migrations.AddField( + model_name='membertask', + name='placeholder', + field=cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, slotname='task_placeholder', to='cms.Placeholder'), + ), + migrations.AlterField( + model_name='membertask', + name='image', + field=project.utils.CroppableFilerImageField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.FILER_IMAGE_MODEL, verbose_name='Bild'), + ), + migrations.AlterField( + model_name='profile', + name='first_name', + field=models.CharField(default='', max_length=255, verbose_name='Vorname'), + preserve_default=False, + ), + migrations.AlterField( + model_name='profile', + name='last_name', + field=models.CharField(default='', max_length=255, verbose_name='Nachname'), + preserve_default=False, + ), + migrations.DeleteModel( + name='MemberTaskFormField', + ), + migrations.DeleteModel( + name='MemberTaskRegistration', + ), + migrations.AddField( + model_name='membertasktranslation', + name='master', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='memberzone.MemberTask'), + ), + migrations.AddField( + model_name='memberdownloadtagtranslation', + name='master', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='memberzone.MemberDownloadTag'), + ), + migrations.AddField( + model_name='memberdownloadsectiontranslation', + name='master', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='memberzone.MemberDownloadSection'), + ), + migrations.AddField( + model_name='memberdownloadfiletranslation', + name='master', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='translations', to='memberzone.MemberDownloadFile'), + ), + migrations.AlterUniqueTogether( + name='membertasktranslation', + unique_together=set([('language_code', 'master')]), + ), + migrations.AlterUniqueTogether( + name='memberdownloadtagtranslation', + unique_together=set([('language_code', 'master')]), + ), + migrations.AlterUniqueTogether( + name='memberdownloadsectiontranslation', + unique_together=set([('language_code', 'master')]), + ), + migrations.AlterUniqueTogether( + name='memberdownloadfiletranslation', + unique_together=set([('language_code', 'master')]), + ), + ] diff --git a/src/memberzone/migrations/0004_membertask_published.py b/src/memberzone/migrations/0004_membertask_published.py new file mode 100644 index 0000000..af806c1 --- /dev/null +++ b/src/memberzone/migrations/0004_membertask_published.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-03-22 11:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('memberzone', '0003_auto_20180322_1052'), + ] + + operations = [ + migrations.AddField( + model_name='membertask', + name='published', + field=models.BooleanField(default=False, verbose_name='Veröffentlichen'), + ), + ] diff --git a/src/memberzone/migrations/0005_auto_20180322_1205.py b/src/memberzone/migrations/0005_auto_20180322_1205.py new file mode 100644 index 0000000..a1b2cbd --- /dev/null +++ b/src/memberzone/migrations/0005_auto_20180322_1205.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-03-22 12:05 +from __future__ import unicode_literals + +import cms.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('memberzone', '0004_membertask_published'), + ] + + operations = [ + migrations.AddField( + model_name='membertask', + name='informed_users', + field=models.ManyToManyField(blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='Als gelesen markiert von:'), + ), + migrations.AlterField( + model_name='membertask', + name='placeholder', + field=cms.models.fields.PlaceholderField(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, slotname='content', to='cms.Placeholder'), + ), + ] diff --git a/src/memberzone/models.py b/src/memberzone/models.py index 77f0a92..2c0bcdf 100644 --- a/src/memberzone/models.py +++ b/src/memberzone/models.py @@ -1,18 +1,18 @@ +from cms.models.fields import PlaceholderField +import os from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.core.files.storage import default_storage -from djangocms_text_ckeditor.fields import HTMLField -from django import forms from django.conf import settings from django.db import models from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from filer.fields.folder import FilerFolderField -from filer.fields.image import FilerImageField -from filer.models import Image as ImageModel +from image_cropping import ImageRatioField +from parler.models import TranslatableModel, TranslatedFields from memberzone.storage import PrivateS3MediaStorage +from project.utils import CroppableFilerImageField class Profile(models.Model): @@ -25,149 +25,69 @@ class Profile(models.Model): email = models.EmailField(verbose_name=_('E-Mail'), null=True, blank=True) class Meta: - verbose_name = _('User Profil') - verbose_name_plural = _('User Profile') + verbose_name = 'User Profil' + verbose_name_plural = 'User Profile' def __str__(self): + return self.full_name + + @property + def full_name(self): return '{} {}'.format(self.first_name, self.last_name) -class MemberTask(models.Model): - groups = models.ManyToManyField(Group, verbose_name=_('Mitglieder Gruppe'), related_name='tasks') - title = models.CharField(max_length=100, verbose_name=_('Title')) - slug = models.SlugField(verbose_name=_('slug'), unique=True) - sub_title = models.CharField(max_length=100, verbose_name=_('Untertitle'), blank=True) - bodytext = HTMLField(verbose_name=_('Inhalt'), configuration='simple_ckeditor') - image = FilerImageField(verbose_name=_('Bild'), blank=True, null=True) - folder = FilerFolderField(verbose_name=_('Bilder'), blank=True, null=True) - publish_date = models.DateTimeField(verbose_name=_('Veröffentlichungsdatum'), default=timezone.now) - deadline = models.DateTimeField(verbose_name=_('Anmeldeschluss'), blank=True, null=True) +class MemberTask(TranslatableModel): + groups = models.ManyToManyField(Group, verbose_name='Mitglieder Gruppe', related_name='tasks') + image = CroppableFilerImageField(verbose_name='Bild', blank=True, null=True) + cropping = ImageRatioField('image', '1200x800', free_crop=True) + + placeholder = PlaceholderField('content') + + published = models.BooleanField(verbose_name='Veröffentlicht', default=False) + publish_date = models.DateTimeField(verbose_name='Veröffentlichungsdatum', default=timezone.now) - max_num = models.IntegerField(verbose_name=_('max. Formulare'), default=1) - submit_text = models.CharField(max_length=100, verbose_name=_('Formular Button Text'), - default=_('Als gelesen markieren')) - form_bodytext = HTMLField(verbose_name=_('Formular Beschreibung'), configuration='simple_ckeditor', blank=True, - default='

Jetzt anmelden

') + informed_users = models.ManyToManyField(get_user_model(), verbose_name='Als gelesen markiert von:', + null=True, blank=True) - success_title = models.CharField(verbose_name=_('Bestätigungstitel'), max_length=200, - default=_('Ihre Anmeldung wurde erfasst')) - success_bodytext = HTMLField(verbose_name=_('Bestätigungstext'), configuration='simple_ckeditor') + translations = TranslatedFields( + title=models.CharField(max_length=100, verbose_name=_('Title')), + ) class Meta: - verbose_name = _('Mitglieder Aufgabe') - verbose_name_plural = _('Mitglieder Aufgaben') + verbose_name = 'Mitglieder Aufgabe' + verbose_name_plural = 'Mitglieder Aufgaben' ordering = ['-publish_date'] def __str__(self): return self.title - @property - def images(self): - return [x for x in self.folder.files if isinstance(x, ImageModel)] - def get_absolute_url(self): - return reverse_lazy('memberzone:task', args=[self.slug]) - - @property - def is_expired(self): - return self.deadline and self.deadline <= timezone.now() - - @property - def form_class(self): - fields = self.fields.all() - - class MyDynamicForm(forms.Form): - def __init__(self, *args, **kwargs): - super(MyDynamicForm, self).__init__(*args, **kwargs) - for f in fields: - self.fields[f.name.replace('-', '_')] = f.form_field - - return MyDynamicForm - - -class MemberTaskFormField(models.Model): - FIELD_TYPES = [ - ('text', _('Text Feld (einzeilig)')), - ('textarea', _('Text Feld (mehrzeilig)')), - ('email', _('E-Mail Feld')), - ('radio', _('Auswahlfeld (eine auswahl)')), - ('checkbox', _('Auswahlfeld (mehrere auswahlen)')), - ] - task = models.ForeignKey(MemberTask, related_name='fields', verbose_name=_('Mitglieder Aufgabe')) - type = models.CharField(max_length=30, choices=FIELD_TYPES) - label = models.CharField(max_length=100, verbose_name=_('Label')) - name = models.SlugField(verbose_name=_('Name')) - choices = models.TextField(verbose_name=_('Auswahlwerte'), - help_text=_( - 'Werte die bei einem Auswahlfeld zur verfügung stehen sollen. Ein wert pro Zeile'), - blank=True) - required = models.BooleanField(default=False, verbose_name=_('Pflichtfeld')) - - ordering = models.IntegerField(default=50, verbose_name=_('Sortierung')) - - class Meta: - verbose_name = _('Formularfeld') - verbose_name_plural = _('Formularfelder') - ordering = ['ordering'] - unique_together = ['task', 'name'] - - def __str__(self): - return self.label - - @property - def form_choices(self): - return [(x, x) for x in self.choices.splitlines()] - - @property - def form_field(self): - if self.type == 'email': - return forms.EmailField(label=self.label, required=self.required) - elif self.type == 'textarea': - return forms.CharField(label=self.label, required=self.required, widget=forms.Textarea()) - elif self.type == 'radio': - return forms.ChoiceField(label=self.label, required=self.required, choices=self.form_choices, - widget=forms.RadioSelect) - elif self.type == 'checkbox': - return forms.MultipleChoiceField(label=self.label, required=self.required, choices=self.form_choices, - widget=forms.CheckboxSelectMultiple) - else: - return forms.CharField(label=self.label, required=self.required) + return reverse_lazy('memberzone:task', args=[self.pk]) -class MemberTaskRegistration(models.Model): - task = models.ForeignKey(MemberTask, related_name='registrations', verbose_name=_('Mitglieder Aufgabe')) - user = models.ForeignKey(get_user_model(), related_name='registrations') - form_data = models.TextField(verbose_name=_('Form Content')) - cdate = models.DateTimeField(auto_now_add=True) +class MemberDownloadSection(TranslatableModel): + translations = TranslatedFields( + title=models.CharField(max_length=100, verbose_name='Title') + ) + ordering = models.IntegerField(default=50, verbose_name='Sortierung') class Meta: - verbose_name = _('Mitglieder Aufgabe Registierung') - verbose_name_plural = _('Mitglieder Aufgaben Registierungen') - unique_together = ['user', 'task'] - - def __str__(self): - return '{} {}'.format(self.user, self.task) - - -class MemberDownloadSection(models.Model): - title = models.CharField(max_length=100, verbose_name=_('Title')) - ordering = models.IntegerField(default=50, verbose_name=_('Sortierung')) - - class Meta: - verbose_name = _('Mitglieder Download Section') - verbose_name_plural = _('Mitglieder Download Sections') + verbose_name = 'Mitglieder Download Section' + verbose_name_plural = 'Mitglieder Download Sections' ordering = ['ordering'] def __str__(self): return self.title -class MemberDownloadTag(models.Model): - name = models.CharField(max_length=100, verbose_name=_('Name')) +class MemberDownloadTag(TranslatableModel): + translations = TranslatedFields( + name=models.CharField(max_length=100, verbose_name='Name') + ) class Meta: - verbose_name = _('Mitglieder Download Tag') - verbose_name_plural = _('Mitglieder Download Tags') + verbose_name = 'Mitglieder Download Tag' + verbose_name_plural = 'Mitglieder Download Tags' def __str__(self): return self.name @@ -179,22 +99,24 @@ else: protected_file_storage = default_storage -class MemberDownloadFile(models.Model): - section = models.ForeignKey(MemberDownloadSection, verbose_name=_('Download Section'), related_name='files') - groups = models.ManyToManyField(Group, verbose_name=_('Mitgliedergruppen'), related_name='files') - tags = models.ManyToManyField(MemberDownloadTag, verbose_name=_('Suchbegriffe'), related_name='files', blank=True, - null=True) +class MemberDownloadFile(TranslatableModel): + section = models.ForeignKey(MemberDownloadSection, verbose_name='Download Section', related_name='files') + groups = models.ManyToManyField(Group, verbose_name='Mitgliedergruppen', related_name='files') + tags = models.ManyToManyField(MemberDownloadTag, verbose_name='Suchbegriffe', related_name='files', + blank=True, null=True) - name = models.CharField(max_length=512, verbose_name=_('Name'), blank=True, null=True) - description = models.TextField(verbose_name=_('Beschreibung'), blank=True, null=True) - file = models.FileField(upload_to='protected_files', max_length=512, storage=protected_file_storage) + translations = TranslatedFields( + name=models.CharField(max_length=512, verbose_name='Name', blank=True, null=True), + description=models.TextField(verbose_name='Beschreibung', blank=True, null=True), + file=models.FileField(upload_to='protected_files', max_length=512, storage=protected_file_storage) + ) - ordering = models.IntegerField(default=50, verbose_name=_('Sortierung')) + ordering = models.IntegerField(default=50, verbose_name='Sortierung') class Meta: - verbose_name = _('Mitglieder Download') - verbose_name_plural = _('Mitglieder Downloads') - ordering = ['ordering', 'name'] + verbose_name = 'Mitglieder Download' + verbose_name_plural = 'Mitglieder Downloads' + ordering = ['ordering'] def __str__(self): return self.label @@ -212,8 +134,8 @@ class MemberDownloadFile(models.Model): if self.name: return self.name else: - return self.file.name + return os.path.basename(self.file.name) @property def tag_list(self): - return ', '.join(list(self.tags.values_list('name', flat=True))) + return ', '.join(list(self.tags.values_list('translations__name', flat=True))) diff --git a/src/memberzone/templates/memberzone/change_form.html b/src/memberzone/templates/memberzone/change_form.html new file mode 100644 index 0000000..5914cc8 --- /dev/null +++ b/src/memberzone/templates/memberzone/change_form.html @@ -0,0 +1,13 @@ +{% load i18n %} +
+ {% csrf_token %} +
+ {% for field in form %} + {% include 'project/includes/field.html' with field=field label=True %} + {% endfor %} +
+ +
diff --git a/src/memberzone/templates/memberzone/change_password.html b/src/memberzone/templates/memberzone/change_password.html deleted file mode 100644 index 8e5db3a..0000000 --- a/src/memberzone/templates/memberzone/change_password.html +++ /dev/null @@ -1,16 +0,0 @@ -{% load i18n %} -
- {% csrf_token %} -
- {% include 'project/content/includes/_field.html' with field=form.old_password label=True small=True %} - {% include 'project/content/includes/_field.html' with field=form.new_password1 label=True small=True %} - {% include 'project/content/includes/_field.html' with field=form.new_password2 label=True small=True %} -
- -
diff --git a/src/memberzone/templates/memberzone/change_password_done.html b/src/memberzone/templates/memberzone/change_password_done.html deleted file mode 100644 index bf3a1f9..0000000 --- a/src/memberzone/templates/memberzone/change_password_done.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n %} -
- {% trans 'Ihr Passwort wurden erfolgreich geändert' %} - - - {% include 'project/assets/close.svg' %} - {% trans 'Schliessen' %} - - {% include 'project/assets/close.svg' %} - -
\ No newline at end of file diff --git a/src/memberzone/templates/memberzone/change_success.html b/src/memberzone/templates/memberzone/change_success.html new file mode 100644 index 0000000..2c42550 --- /dev/null +++ b/src/memberzone/templates/memberzone/change_success.html @@ -0,0 +1,8 @@ +{% load i18n %} +
+

{% trans 'Ihre Informationen wurden erfolgreich angepasst.' %}

+ + {% include 'project/assets/close.svg' %} + {% trans 'Schliessen' %} + +
\ No newline at end of file diff --git a/src/memberzone/templates/memberzone/email/member_registration_confirmation_email.html b/src/memberzone/templates/memberzone/email/member_registration_confirmation_email.html deleted file mode 100644 index 7a7f5b5..0000000 --- a/src/memberzone/templates/memberzone/email/member_registration_confirmation_email.html +++ /dev/null @@ -1,14 +0,0 @@ - -{{ subject }} - -

{{ subject }}

-
-{% for data_set in data %} - -{% endfor %} - - diff --git a/src/memberzone/templates/memberzone/includes/_downloads.html b/src/memberzone/templates/memberzone/includes/_downloads.html deleted file mode 100644 index d24826b..0000000 --- a/src/memberzone/templates/memberzone/includes/_downloads.html +++ /dev/null @@ -1,57 +0,0 @@ -{% load i18n static util_tags thumbnail %} - - -
-
- {% if title != False %} -

{% trans title %}

- {% endif %} - {% if search %} -
-
- - -
-
- {% endif %} - {% for section in download_sections %} - - {% endfor %} -
-
\ No newline at end of file diff --git a/src/memberzone/templates/memberzone/includes/_field.html b/src/memberzone/templates/memberzone/includes/_field.html deleted file mode 100644 index 0563722..0000000 --- a/src/memberzone/templates/memberzone/includes/_field.html +++ /dev/null @@ -1,8 +0,0 @@ - \ No newline at end of file diff --git a/src/memberzone/templates/memberzone/overview.html b/src/memberzone/templates/memberzone/overview.html index d6e9ceb..b5bab73 100644 --- a/src/memberzone/templates/memberzone/overview.html +++ b/src/memberzone/templates/memberzone/overview.html @@ -1,160 +1,82 @@ {% extends 'main.html' %} -{% load i18n cms_tags thumbnail %} - -{% load i18n static task_tags %} - +{% load i18n static %} +{% block title %}{% trans 'Mitgliederbereich' %}{% endblock %} {% block content %}
- {% include 'project/includes/content_intro.html' %} +
+
+

{% trans 'Grüezi, ' %}{{ request.user.profile.full_name }}

+

{% trans 'Willkommen in Ihrem persönlichen Portal der Tagesschule Elementa' %}

+ + {% include 'project/assets/arrow-left-long.svg' %} + {% trans 'Logout' %} + +
+
+
- - +
-
+ -
- {% include 'memberzone/includes/_downloads.html' with search=True title='Downloads' %} - +
+

{% trans 'Downloads' %}

+
+
+ + +
+
+ {% for section in download_sections %} +
+
+

{{ section.title }}

+
+ {% include 'project/plugins/content/download_section.html' with instance=section %} +
+ {% endfor %} +
+
{% endblock %} \ No newline at end of file diff --git a/src/memberzone/templates/memberzone/profile_edit.html b/src/memberzone/templates/memberzone/profile_edit.html deleted file mode 100644 index 399b887..0000000 --- a/src/memberzone/templates/memberzone/profile_edit.html +++ /dev/null @@ -1,17 +0,0 @@ -{% load i18n %} -
- {% csrf_token %} -
- {% include 'memberzone/includes/_field.html' with field=form.gender label=True small=True %} - {% include 'memberzone/includes/_field.html' with field=form.title label=True small=True %} - {% include 'memberzone/includes/_field.html' with field=form.first_name label=True small=True %} - {% include 'memberzone/includes/_field.html' with field=form.last_name label=True small=True %} -
- -
diff --git a/src/memberzone/templates/memberzone/profile_edit_done.html b/src/memberzone/templates/memberzone/profile_edit_done.html deleted file mode 100644 index b670f65..0000000 --- a/src/memberzone/templates/memberzone/profile_edit_done.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n %} - \ No newline at end of file diff --git a/src/memberzone/templates/memberzone/task.html b/src/memberzone/templates/memberzone/task.html index afbfa21..a00d76d 100644 --- a/src/memberzone/templates/memberzone/task.html +++ b/src/memberzone/templates/memberzone/task.html @@ -1,139 +1,44 @@ {% extends 'project/content.html' %} -{% load i18n static thumbnail task_tags %} -https://source.unsplash.com/random/1200x900 - -{% block body_class %}header-close{% endblock %} -{% block header_menu_url %}{% url 'memberzone:overview' %}{% endblock %} - -{% block intro_class %}intro--image{% endblock %} -{% block intro_image %}{% if object.image %} - {% thumbnail object.image "1200x900" crop="center" subject_location=object.image.subject_location %} -{% endif %}{% endblock %} - -{% block content_content %} -
-
-
-
{{ object.bodytext | add_animation_classes | safe }}
-
+{% load i18n thumbnail cms_tags %} + +{% block title %}{{ object.title }}{% endblock %} + +{% block content_intro %} +
+ + {% if object.image %} + {% thumbnail object.image 1600x800 box=object.cropping crop detail as thumb %} +
+ {% endif %}
- - {% include 'project/content/includes/_grid.html' with grid=grid last=True %} - -
-
-
-
-
- {% if formset and not object.is_expired %} - {{ object.form_bodytext | add_animation_classes | safe }} - {% if formset.errors %} - - {% trans 'Bitte korrigieren Sie die markierten Felder.' %} - - {% endif %} - {% if formset.non_form_errors %} - - {{ formset.non_form_errors }} - - {% endif %} -
- {% csrf_token %} - {{ formset.management_form }} - -
- {% for form in formset %} -
- {% if object.max_num > 1 %} -
-

{{ forloop.counter }}. - Person

- {% if forloop.counter0 > 0 %} - - {% include 'project/assets/close.svg' %} - {% trans 'Entfernen' %} - - {% endif %} -
- {% endif %} - - {% if form.non_field_errors %} - - {{ formset.non_field_errors }} - - {% endif %} - - {% for field in form %} - {% if field.name == 'DELETE' %} - {% include 'project/content/includes/_field.html' with field=field label=True hidden=True %} - {% else %} - {% include 'project/content/includes/_field.html' with field=field label=True %} - {% endif %} - {% endfor %} -
- {% endfor %} -
- - - -
- - {% if object.max_num > 1 %} - - - {% include 'project/assets/plus.svg' %} - {% trans 'Weitere Person hinzufügen' %} - - - {% endif %} -
-
- {% else %} - {% if object.is_expired %} -

{% trans 'Die Anmeldungsfrist abgelaufen' %}

-

- Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor - invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et - accusam et -

- {% else %} -

{% trans "Bereits abgeschlossen" %}

-

- Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor - invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et - accusam et -

- {% endif %} - {% endif %} -
-
+{% endblock %} + +{% block navigation_title %}{{ object.title }}{% endblock %} + +{% block content_main %} + {% render_placeholder object.placeholder language LANGUAGE_CODE %} + +
+ {% csrf_token %} + +
+ + {% include 'project/assets/arrow-left-long.svg' %} + {% trans 'Zurück zur Übersicht' %} + + {% if not request.user in object.informed_users.all %} + + {% endif %}
-
- + {% endblock %} \ No newline at end of file diff --git a/src/memberzone/templates/memberzone/task_success.html b/src/memberzone/templates/memberzone/task_success.html deleted file mode 100644 index 7668c31..0000000 --- a/src/memberzone/templates/memberzone/task_success.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends 'project/dialog.html' %} -{% load i18n static %} - -{% block extra_meta %} - -{% endblock %} - -{% block dialog_content %} - -{% endblock %} \ No newline at end of file diff --git a/src/memberzone/templates/registration/login.html b/src/memberzone/templates/registration/login.html index eb63eeb..465f99c 100644 --- a/src/memberzone/templates/registration/login.html +++ b/src/memberzone/templates/registration/login.html @@ -1,16 +1,36 @@ -{% extends 'main.html' %} +{% extends 'project/dialog.html' %} {% load i18n %} +{% block title %}{% trans 'Mitgliederbereich' %}{% endblock %} -{% block content %} -
-
-
-
- {% csrf_token %} - {{ form }} -
-
+{% block extra_meta %} + +{% endblock %} + +{% block dialog %} +

{% trans 'Login' %}

+

+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr. +

+
+ {% if form.non_field_errors %} + {% for error in form.non_field_errors %} +

{{ error }}

+ {% endfor %} + {% endif %} + + {% csrf_token %} + + {% for field in form %} + {% include 'project/includes/field.html' with field=field %} + {% endfor %} + +
+
-
+ + {% endblock %} diff --git a/src/memberzone/templatetags/__init__.py b/src/memberzone/templatetags/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/memberzone/templatetags/task_tags.py b/src/memberzone/templatetags/task_tags.py deleted file mode 100644 index cc5258d..0000000 --- a/src/memberzone/templatetags/task_tags.py +++ /dev/null @@ -1,34 +0,0 @@ -import lxml.html -from django import template -from django.template.defaultfilters import stringfilter - -from memberzone.models import MemberTaskRegistration - -register = template.Library() - - -@register.filter(name='task_status') -def task_user_status(task, user): - try: - MemberTaskRegistration.objects.get(task=task, user=user) - except MemberTaskRegistration.DoesNotExist: - if task.is_expired: - return 'control__item--status--expired' - else: - return '' - else: - return 'control__item--status--active' - - -@register.filter -@stringfilter -def add_animation_classes(input_html): - document = lxml.html.fromstring('
{}
'.format(input_html)) - for el in document.xpath('//p|ul|ol|h2|h3'): - classes = el.attrib.get('class', '').split(' ') - if 'reveal' not in classes: - classes.append('reveal') - if 'reveal_animation' not in classes: - classes.append('reveal_animation') - el.attrib['class'] = ' '.join(classes) - return lxml.html.tostring(document)[5:-6] diff --git a/src/memberzone/urls.py b/src/memberzone/urls.py index de5fcf7..d81e415 100644 --- a/src/memberzone/urls.py +++ b/src/memberzone/urls.py @@ -10,35 +10,29 @@ from memberzone.forms import LoginForm from memberzone.views import OverviewView, MemberTaskDetailView, MemberTaskDetailSuccessView, ProfileEditView urlpatterns = [ - url(_(r'^login/$'), LoginView.as_view( - success_url=reverse_lazy('memberzone:overview'), - form_class=LoginForm - ), name='login'), - url(_(r'^logout/$'), LogoutView.as_view(next_page='login'), name='logout'), + url(_(r'^login/$'), LoginView.as_view(form_class=LoginForm), name='login'), + + url(_(r'^logout/$'), LogoutView.as_view(next_page=reverse_lazy('memberzone:overview')), name='logout'), url(_(r'^account/change/password/$'), login_required(PasswordChangeView.as_view( - template_name='memberzone/change_password.html', + template_name='memberzone/change_form.html', success_url=reverse_lazy('memberzone:change_password_done') ), login_url=reverse_lazy('memberzone:login')), name='change_password'), - url(_(r'^account/change/password/succeeded/$'), login_required(TemplateView.as_view( - template_name='memberzone/change_password_done.html' + url(_(r'^account/change/password/success/$'), login_required(TemplateView.as_view( + template_name='memberzone/change_success.html' ), login_url=reverse_lazy('memberzone:login')), name='change_password_done'), - url(_(r'^account/edit/$'), login_required(ProfileEditView.as_view( + url(_(r'^profile/edit/$'), login_required(ProfileEditView.as_view( ), login_url=reverse_lazy('memberzone:login')), name='profile_edit'), - url(_(r'^account/edit/succeeded/$'), login_required(TemplateView.as_view( - template_name='memberzone/profile_edit_done.html' + url(_(r'^profile/edit/success/$'), login_required(TemplateView.as_view( + template_name='memberzone/change_success.html' ), login_url=reverse_lazy('memberzone:login')), name='profile_edit_done'), - url(_(r'^task/(?P[\w-]+)/$'), login_required(MemberTaskDetailView.as_view( + url(_(r'^task/(?P\d+)/$'), login_required(MemberTaskDetailView.as_view( ), login_url=reverse_lazy('memberzone:login')), name='task'), - url(_(r'^task/(?P[\w-]+)/success/$'), - login_required(MemberTaskDetailSuccessView.as_view(), login_url=reverse_lazy('memberzone:login')), - name='task_success'), - url(_(r'^overview/$'), login_required(OverviewView.as_view(), login_url=reverse_lazy('memberzone:login')), name='overview'), ] diff --git a/src/memberzone/views.py b/src/memberzone/views.py index 097ab86..f5f1312 100644 --- a/src/memberzone/views.py +++ b/src/memberzone/views.py @@ -1,36 +1,26 @@ -import json - -from django.conf import settings -from django.contrib.auth.forms import SetPasswordForm -from django.contrib.auth.tokens import default_token_generator -from django.contrib.auth.views import LoginView as DjangoLoginView -from django.contrib.sites.shortcuts import get_current_site -from django.core.mail import EmailMessage -from django.db.models import Q -from django.forms import formset_factory -from django.http import HttpResponseRedirect -from django.shortcuts import resolve_url -from django.template.loader import render_to_string -from django.urls import reverse, reverse_lazy +from django.shortcuts import redirect +from django.urls import reverse_lazy from django.utils import timezone -from django.utils.encoding import force_bytes, force_text -from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode from django.utils.translation import ugettext_lazy as _ -from django.views.generic import UpdateView, ListView, DetailView, FormView +from django.views.generic import UpdateView, ListView, DetailView -from memberzone.models import MemberTask, MemberTaskRegistration, MemberDownloadFile, Profile +from memberzone.forms import ProfileEditForm +from memberzone.models import MemberTask, MemberDownloadFile class MemeberTaskQuerysetMixin(object): def get_queryset(self): - return MemberTask.objects.filter(groups__in=self.request.user.groups.all()).distinct() + queryset = MemberTask.objects.filter(groups__in=self.request.user.groups.all()) + if not self.request.user.is_superuser: + queryset = queryset.filter(published=True) + return queryset class OverviewView(MemeberTaskQuerysetMixin, ListView): template_name = 'memberzone/overview.html' open_tasks = None - paginate_by = 3 + paginate_by = 5 def get_title(self): return _('Grüezi, {first_name} {last_name}').format( @@ -50,106 +40,46 @@ class OverviewView(MemeberTaskQuerysetMixin, ListView): sections[file.section_id]['items'].append(file) sections_list = [{'title': x['section'].title, 'items': x['items'], 'ordering': x['section'].ordering} for x in - sections.values()] + sections.values()] return sorted(sections_list, key=lambda x: x['ordering']) def get_queryset(self): queryset = super(OverviewView, self).get_queryset() - return queryset.exclude(pk__in=self.get_open_tasks().values_list('pk', flat=True)) + return queryset.filter(informed_users__in=[self.request.user]) def get_open_tasks(self): - yesterday = timezone.now() - timezone.timedelta(days=1) - all_user_registrations = MemberTaskRegistration.objects.filter(user=self.request.user, cdate__lte=yesterday) - queryset = MemeberTaskQuerysetMixin.get_queryset(self) - queryset.exclude(registrations__in=all_user_registrations).order_by('-registrations__cdate') + queryset = MemeberTaskQuerysetMixin.get_queryset(self).exclude(informed_users__in=[self.request.user]) return queryset + def get_settings(self): + settings = ( + (_('Benutzerdaten'), reverse_lazy('memberzone:profile_edit')), + (_('Passwort'), reverse_lazy('memberzone:change_password')), + ) + return settings + def get_context_data(self, **kwargs): context = super(OverviewView, self).get_context_data(**kwargs) context.update({ 'download_sections': self.get_download_sections(), - 'open_tasks': None if self.request.GET.get(self.page_kwarg, None) else self.get_open_tasks(), + 'settings': self.get_settings() }) + self.open_tasks = self.get_open_tasks() + if not self.request.GET.get(self.page_kwarg, None) and self.open_tasks.count() > 0: + context.update({ + 'object_list': self.open_tasks, + 'open_object_list': True + }) return context class MemberTaskDetailView(MemeberTaskQuerysetMixin, DetailView): template_name = 'memberzone/task.html' - model = MemberTask - - def get_title(self): - return self.object.title - - def get_description(self): - return self.object.sub_title - - def get_formset(self): - try: - MemberTaskRegistration.objects.get(task=self.object, user=self.request.user) - except MemberTaskRegistration.DoesNotExist: - MemeberTaskFormset = formset_factory( - self.object.form_class, - extra=0, - min_num=1, - max_num=self.object.max_num, - can_delete=True - ) - formset_kwargs = {} - if self.request.method == 'POST': - formset_kwargs.update({ - 'data': self.request.POST - }) - return MemeberTaskFormset(**formset_kwargs) - else: - return None - - def formset_valid(self, formset): - registration = MemberTaskRegistration.objects.create( - task=self.object, - user=self.request.user, - form_data=json.dumps(formset.cleaned_data) - ) - if registration.task.fields.count() > 0: - self.send_confirmation_email(registration=registration) - return HttpResponseRedirect(reverse('memberzone:task_success', args=[self.object.slug])) - - def formset_invalid(self, formset): - return self.render_to_response(context=self.get_context_data(formset=formset, object=self.object)) - - def get(self, request, *args, **kwargs): - self.object = self.get_object() - formset = self.get_formset() - return self.render_to_response(context=self.get_context_data(formset=formset, object=self.object)) - - def send_confirmation_email(self, registration): - subject = _('Anmeldebestätigung: {}').format(registration.task.title) - - confirmation_data = [] - for data_set in json.loads(registration.form_data): - confirmation_data_set = [] - for field in registration.task.fields.all(): - value = data_set.get(field.name.replace('-', '_'), '') - if isinstance(value, list): - value = ','.join(value) - confirmation_data_set.append((field.label, value,)) - confirmation_data.append(confirmation_data_set) - - message = render_to_string('memberzone/email/member_registration_confirmation_email.html', { - 'data': confirmation_data, - 'subject': subject, - }) - msg = EmailMessage(subject, message, settings.DEFAULT_FROM_EMAIL, to=[registration.user.email]) - msg.content_subtype = "html" - # msg.send() def post(self, request, *args, **kwargs): - self.object = self.get_object() - formset = self.get_formset() - if formset.is_valid(): - return self.formset_valid(formset) - else: - return self.formset_invalid(formset) + self.get_object().informed_users.add(self.request.user) + return redirect('memberzone:overview') class MemberTaskDetailSuccessView(MemeberTaskQuerysetMixin, DetailView): @@ -161,9 +91,8 @@ class MemberTaskDetailSuccessView(MemeberTaskQuerysetMixin, DetailView): class ProfileEditView(UpdateView): - model = Profile - fields = ['first_name', 'last_name', 'street', 'zip', 'place', 'email'] - template_name = 'memberzone/profile_edit.html' + form_class = ProfileEditForm + template_name = 'memberzone/change_form.html' success_url = reverse_lazy('memberzone:profile_edit_done') def get_object(self, queryset=None): diff --git a/src/project/templates/main.html b/src/project/templates/main.html index fbd1ed3..afedab5 100644 --- a/src/project/templates/main.html +++ b/src/project/templates/main.html @@ -79,36 +79,38 @@ {% endfor %} - {% if notification %} - - {% trans 'Aktuell' %} - - {% include 'project/assets/bell.svg' %} - - - {% endif %} - - - {% trans 'Suche' %} - - {% include 'project/assets/search.svg' %} - - + {% block header_items %} + {% if notification %} + + {% trans 'Aktuell' %} + + {% include 'project/assets/bell.svg' %} + + + {% endif %} - {% url 'search' as search_url %} - {% with request.get_full_path as current_url %} - - {% trans 'Menü' %} + + {% trans 'Suche' %} - {% include 'project/assets/menu.svg' %} + {% include 'project/assets/search.svg' %} - {% endwith %} -
- {% include 'project/includes/meta_navigation.html' %} -
+ {% url 'search' as search_url %} + {% with request.get_full_path as current_url %} + + {% trans 'Menü' %} + + {% include 'project/assets/menu.svg' %} + + + {% endwith %} + +
+ {% include 'project/includes/meta_navigation.html' %} +
+ {% endblock %}
diff --git a/src/project/templates/project/assets/arrow-down.svg b/src/project/templates/project/assets/arrow-down.svg deleted file mode 100644 index 8d7faf7..0000000 --- a/src/project/templates/project/assets/arrow-down.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - \ No newline at end of file diff --git a/src/project/templates/project/assets/arrow-left-long.svg b/src/project/templates/project/assets/arrow-left-long.svg new file mode 100644 index 0000000..e2e6ccf --- /dev/null +++ b/src/project/templates/project/assets/arrow-left-long.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/project/templates/project/assets/arrow-left.svg b/src/project/templates/project/assets/arrow-left.svg index e0246d0..07940a8 100644 --- a/src/project/templates/project/assets/arrow-left.svg +++ b/src/project/templates/project/assets/arrow-left.svg @@ -1,4 +1,5 @@ - + \ No newline at end of file diff --git a/src/project/templates/project/assets/arrow-right-long.svg b/src/project/templates/project/assets/arrow-right-long.svg index 8d5ca60..34529bd 100644 --- a/src/project/templates/project/assets/arrow-right-long.svg +++ b/src/project/templates/project/assets/arrow-right-long.svg @@ -1,6 +1,5 @@ - - + \ No newline at end of file diff --git a/src/project/templates/project/assets/arrow-right.svg b/src/project/templates/project/assets/arrow-right.svg index d5f2d17..a6e2239 100644 --- a/src/project/templates/project/assets/arrow-right.svg +++ b/src/project/templates/project/assets/arrow-right.svg @@ -1,4 +1,5 @@ - + \ No newline at end of file diff --git a/src/project/templates/project/assets/close.svg b/src/project/templates/project/assets/close.svg index c414147..dc9e1ed 100644 --- a/src/project/templates/project/assets/close.svg +++ b/src/project/templates/project/assets/close.svg @@ -1,7 +1,5 @@ - - + \ No newline at end of file diff --git a/src/project/templates/project/assets/dots.svg b/src/project/templates/project/assets/dots.svg index 4a32d0c..0d4fb70 100644 --- a/src/project/templates/project/assets/dots.svg +++ b/src/project/templates/project/assets/dots.svg @@ -1,6 +1,4 @@ - - - + \ No newline at end of file diff --git a/src/project/templates/project/assets/download.svg b/src/project/templates/project/assets/download.svg index 9b71762..640de7b 100644 --- a/src/project/templates/project/assets/download.svg +++ b/src/project/templates/project/assets/download.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/project/templates/project/assets/mail.svg b/src/project/templates/project/assets/mail.svg index f1229f7..f2da17c 100644 --- a/src/project/templates/project/assets/mail.svg +++ b/src/project/templates/project/assets/mail.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/project/templates/project/assets/menu.svg b/src/project/templates/project/assets/menu.svg index ce2a5ec..1a19f5d 100644 --- a/src/project/templates/project/assets/menu.svg +++ b/src/project/templates/project/assets/menu.svg @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/src/project/templates/project/assets/play.svg b/src/project/templates/project/assets/play.svg index 46498ee..e0f6f37 100644 --- a/src/project/templates/project/assets/play.svg +++ b/src/project/templates/project/assets/play.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/project/templates/project/assets/search.svg b/src/project/templates/project/assets/search.svg index ebfce32..44506fe 100644 --- a/src/project/templates/project/assets/search.svg +++ b/src/project/templates/project/assets/search.svg @@ -3,5 +3,5 @@ + x="19.3033009" y="23.8033009" width="12" height="3"> \ No newline at end of file diff --git a/src/project/templates/project/assets/tick.svg b/src/project/templates/project/assets/tick.svg index 0261179..e0607eb 100644 --- a/src/project/templates/project/assets/tick.svg +++ b/src/project/templates/project/assets/tick.svg @@ -1,4 +1,5 @@ - - + \ No newline at end of file diff --git a/src/project/templates/project/content.html b/src/project/templates/project/content.html index a8987f7..b6d4588 100644 --- a/src/project/templates/project/content.html +++ b/src/project/templates/project/content.html @@ -4,14 +4,14 @@ {% block content %}
{% block content_intro %} - {% include 'project/includes/content_intro.html' %} + {% include 'project/includes/content_intro.html' with image=request.current_page.imageextension.image cropping=request.current_page.imageextension.cropping %} {% endblock %}
- {{ request.current_page }} + {% block navigation_title %}{{ request.current_page }}{% endblock %}
diff --git a/src/project/templates/project/dialog.html b/src/project/templates/project/dialog.html new file mode 100644 index 0000000..cd1c356 --- /dev/null +++ b/src/project/templates/project/dialog.html @@ -0,0 +1,14 @@ +{% extends 'main.html' %} +{% load i18n %} + + +{% block content %} +
+
+
+ {% block dialog %} + {% endblock %} +
+
+
+{% endblock %} diff --git a/src/project/templates/project/includes/content_intro.html b/src/project/templates/project/includes/content_intro.html index a2990d8..2cfad1a 100644 --- a/src/project/templates/project/includes/content_intro.html +++ b/src/project/templates/project/includes/content_intro.html @@ -1,15 +1,17 @@ {% load i18n cms_tags thumbnail %} -
-

- {% if content_title %} - {{ content_title }} - {% else %} - {% page_attribute page_title %} - {% endif %} -

- {% if request.current_page.imageextension.image %} - {% thumbnail request.current_page.imageextension.image 1600x800 box=request.current_page.imageextension.cropping crop detail as thumb %} +
+
+

+ {% if content_title %} + {{ content_title }} + {% else %} + {% page_attribute page_title %} + {% endif %} +

+
+ {% if image %} + {% thumbnail image 1600x800 box=cropping crop detail as thumb %}
{% endif %} diff --git a/src/project/templates/project/includes/field.html b/src/project/templates/project/includes/field.html new file mode 100644 index 0000000..0336a61 --- /dev/null +++ b/src/project/templates/project/includes/field.html @@ -0,0 +1,18 @@ +
+ {% if label %} + + {% endif %} + {{ field }} + + {% if field.errors %} +

+ {% for error in field.errors %} + {{ error }}{% if not forloop.last %}
{% endif %} + {% endfor %} +

+ {% endif %} + + {% if field.help_text %} +

{{ field.help_text }}

+ {% endif %} +
\ No newline at end of file diff --git a/src/project/templates/project/includes/main_menu.html b/src/project/templates/project/includes/main_menu.html index c13bb5c..a45435d 100644 --- a/src/project/templates/project/includes/main_menu.html +++ b/src/project/templates/project/includes/main_menu.html @@ -4,7 +4,7 @@ {% for n in 'nn' %}
{% for child in children|slice:'3' %} - {% thumbnail child.id|page_image 800x1200 crop=True as thumb %} + {% thumbnail child.id|page_image 800x1200 box=child.id|page_image_cropping crop as thumb %}
diff --git a/src/project/templates/project/plugins/content/download_section.html b/src/project/templates/project/plugins/content/download_section.html index 7fe6451..7ed2886 100644 --- a/src/project/templates/project/plugins/content/download_section.html +++ b/src/project/templates/project/plugins/content/download_section.html @@ -6,15 +6,11 @@ {{ item.extension }} {{ item.label }} - {% if item.description %} - {{ item.description }} - {% endif %} + {{ item.description }} + {{ item.tag_list }} {% include 'project/assets/download.svg' %} - {% if item.tag_list %} - {{ file.tag_list }} - {% endif %} {% endfor %} diff --git a/src/project/templatetags/util_tags.py b/src/project/templatetags/util_tags.py index e076488..84daa2b 100644 --- a/src/project/templatetags/util_tags.py +++ b/src/project/templatetags/util_tags.py @@ -11,3 +11,11 @@ def page_image(id): return Page.objects.get(pk=id).imageextension.image except: return None + + +@register.filter +def page_image_cropping(id): + try: + return Page.objects.get(pk=id).imageextension.cropping + except: + return None diff --git a/src/project/views.py b/src/project/views.py index 82c8f8e..0d5c02a 100644 --- a/src/project/views.py +++ b/src/project/views.py @@ -8,6 +8,7 @@ from project.forms import NewsletterSubscriptionForm class SearchView(AldrynSearchView): template_name = 'project/search.html' + context_object_name = 'search_results' def get_context_data(self, **kwargs): context = super(SearchView, self).get_context_data(**kwargs)