├── .gitignore ├── LICENSE ├── README.md ├── README.rst ├── __init__.py ├── __openerp__.py ├── product_images.py ├── res_config.py ├── security └── ir.model.access.csv ├── static ├── lib │ └── slick │ │ ├── ajax-loader.gif │ │ ├── fonts │ │ ├── slick.eot │ │ ├── slick.svg │ │ ├── slick.ttf │ │ └── slick.woff │ │ ├── slick-theme.css │ │ ├── slick.css │ │ └── slick.min.js └── src │ ├── css │ └── website-image-carousel.css │ └── js │ └── website-image-carousel.js └── views ├── product_images.xml ├── res_config.xml ├── theme.xml └── website_product_image_carousel.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | .*.swp 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 lukebranch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | website_multi_image 2 | =================== 3 | Website Multi Image stores multiple product images in a tab: 'Product Images' under Sales >> Product. These images are displayed on the product view page in a synced carousel (two carousels one large and one small) using OwlCarousel2. 4 | 5 | Website Multi-Image 6 | 7 |

Please use the 9.0 branch:

8 | sudo git clone -b 9.0 https://github.com/OdooCommunityWidgets/website_multi_image.git 9 | This will provide you with the latest working 'stable' version of the module built for the 9.0 branch. Please be advised this module is still under development. 10 | 11 |

Frontend Demo:

12 | 13 | 14 |

Admin Demo:

15 | 16 | 17 | TODO: master 18 | =================== 19 | * Image magnification 20 | * Options 21 | * Magnifier.js: https://github.com/mark-rolich/Magnifier.js 22 | * Zoom (jQuery): https://github.com/jackmoore/zoom/tree/master 23 | * BootstrapMagnify: https://github.com/marcaube/bootstrap-magnify 24 | 25 | * Secondary Image (on Mouseover) (Category list view and grid view) 26 | * Image thumbnail preview widget in admin list view (to replace current download link) 27 | * Attach Images to product variants to allow for image galleries based on product variants. This will be designed to switch galleries based on product variant attributes (eg. colour, size, etc.). 28 | * Add product variant swatch thumbnails in a similar manner to Magento 1.9.1 (eg. t-if from tags or labels). 29 | * Add support for WebRotate360 module website_webrotate360 (*not yet built). 30 | * Add support for importing multiple images from URL or local path as default to allow for a more user-friendly mass update/import of product images in a similar manner to the Magmi project for Magento 31 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Product Multi-Image 2 | =================== 3 | 4 | This module adds multiple product images into 5 | a tab in product.template in a tab called 6 | Product Images, and allows an over-ride 7 | of product.image to allow for a multi-image 8 | carousel for the product view page. 9 | 10 | web_tree_image is recommended to install to get images preview: https://github.com/OCA/web/tree/9.0/web_tree_image 11 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from . import product_images 2 | from . import res_config 3 | -------------------------------------------------------------------------------- /__openerp__.py: -------------------------------------------------------------------------------- 1 | { 2 | 'name': 'Product Multi-Image', 3 | 'category': 'Website', 4 | 'version': '1.0', 5 | 'author': 'Luke Branch, Cristian Sebastian Rocha, Ryan Cole', 6 | 'depends': ['product', 'sale', 'website_sale'], 7 | 'data': [ 8 | 'views/product_images.xml', 9 | 'views/website_product_image_carousel.xml', 10 | 'views/theme.xml', 11 | 'views/res_config.xml', 12 | 'security/ir.model.access.csv', 13 | ], 14 | 'application': True, 15 | } 16 | -------------------------------------------------------------------------------- /product_images.py: -------------------------------------------------------------------------------- 1 | from openerp import models, fields, api 2 | 3 | 4 | class product_image(models.Model): 5 | _name = 'product.image' 6 | _order = 'sequence, id DESC' 7 | 8 | name = fields.Char('Name') 9 | description = fields.Text('Description') 10 | sequence = fields.Integer('Sequence') 11 | image_alt = fields.Text('Image Label') 12 | image = fields.Binary('Image') 13 | image_small = fields.Binary('Small Image') 14 | product_tmpl_id = fields.Many2one('product.template', 'Product', select=True) 15 | from_main_image = fields.Boolean('From Main Image', default=False) 16 | 17 | 18 | class product_product(models.Model): 19 | _inherit = 'product.product' 20 | 21 | images = fields.One2many('product.image', related='product_tmpl_id.images', 22 | string='Images', store=False) 23 | 24 | 25 | class product_template(models.Model): 26 | _inherit = 'product.template' 27 | 28 | images = fields.One2many('product.image', 'product_tmpl_id', 29 | string='Images') 30 | 31 | @api.one 32 | def action_copy_image_to_images(self): 33 | if not self.image: 34 | return 35 | image = None 36 | for r in self.images: 37 | if r.from_main_image: 38 | image = r 39 | break 40 | 41 | if image: 42 | image.image = self.image 43 | else: 44 | vals = {'image': self.image, 45 | 'name': self.name, 46 | 'product_tmpl_id': self.id, 47 | 'from_main_image': True, } 48 | self.env['product.image'].create(vals) 49 | -------------------------------------------------------------------------------- /res_config.py: -------------------------------------------------------------------------------- 1 | from openerp import models, fields, api 2 | 3 | 4 | class website_config_settings(models.TransientModel): 5 | 6 | _inherit = 'website.config.settings' 7 | 8 | @api.one 9 | def action_copy_shop_images(self): 10 | self.env['product.template'].search([]).action_copy_image_to_images() 11 | -------------------------------------------------------------------------------- /security/ir.model.access.csv: -------------------------------------------------------------------------------- 1 | id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 2 | access_product_image_user,access.product_image.user,model_product_image,,1,0,0,0 3 | access_product_image_manager,access.product_image.manager,model_product_image,base.group_sale_manager,1,1,1,1 4 | -------------------------------------------------------------------------------- /static/lib/slick/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OdooCommunityWidgets/website_multi_image/060e978f9b3745c332f14806a00bfa89007fefed/static/lib/slick/ajax-loader.gif -------------------------------------------------------------------------------- /static/lib/slick/fonts/slick.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OdooCommunityWidgets/website_multi_image/060e978f9b3745c332f14806a00bfa89007fefed/static/lib/slick/fonts/slick.eot -------------------------------------------------------------------------------- /static/lib/slick/fonts/slick.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /static/lib/slick/fonts/slick.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OdooCommunityWidgets/website_multi_image/060e978f9b3745c332f14806a00bfa89007fefed/static/lib/slick/fonts/slick.ttf -------------------------------------------------------------------------------- /static/lib/slick/fonts/slick.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OdooCommunityWidgets/website_multi_image/060e978f9b3745c332f14806a00bfa89007fefed/static/lib/slick/fonts/slick.woff -------------------------------------------------------------------------------- /static/lib/slick/slick-theme.css: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | /* Slider */ 3 | .slick-loading .slick-list 4 | { 5 | background: #fff url('/website_multi_image/static/lib/slick/ajax-loader.gif') center center no-repeat; 6 | } 7 | 8 | /* Icons */ 9 | @font-face 10 | { 11 | font-family: 'slick'; 12 | font-weight: normal; 13 | font-style: normal; 14 | 15 | src: url('/website_multi_image/static/lib/slick/fonts/slick.eot'); 16 | src: url('/website_multi_image/static/lib/slick/fonts/slick.eot?#iefix') format('embedded-opentype'), url('/website_multi_image/static/lib/slick/fonts/slick.woff') format('woff'), url('/website_multi_image/static/lib/slick/fonts/slick.ttf') format('truetype'), url('/website_multi_image/static/lib/slick/fonts/slick.svg#slick') format('svg'); 17 | } 18 | /* Arrows */ 19 | .slick-prev, 20 | .slick-next 21 | { 22 | font-size: 0; 23 | line-height: 0; 24 | 25 | position: absolute; 26 | top: 50%; 27 | 28 | display: block; 29 | 30 | width: 20px; 31 | height: 20px; 32 | padding: 0; 33 | -webkit-transform: translate(0, -50%); 34 | -ms-transform: translate(0, -50%); 35 | transform: translate(0, -50%); 36 | 37 | cursor: pointer; 38 | 39 | color: transparent; 40 | border: none; 41 | outline: none; 42 | background: transparent; 43 | } 44 | .slick-prev:hover, 45 | .slick-prev:focus, 46 | .slick-next:hover, 47 | .slick-next:focus 48 | { 49 | color: transparent; 50 | outline: none; 51 | background: transparent; 52 | } 53 | .slick-prev:hover:before, 54 | .slick-prev:focus:before, 55 | .slick-next:hover:before, 56 | .slick-next:focus:before 57 | { 58 | opacity: 1; 59 | } 60 | .slick-prev.slick-disabled:before, 61 | .slick-next.slick-disabled:before 62 | { 63 | opacity: .25; 64 | } 65 | 66 | .slick-prev:before, 67 | .slick-next:before 68 | { 69 | font-family: 'slick'; 70 | font-size: 20px; 71 | line-height: 1; 72 | 73 | opacity: .75; 74 | color: white; 75 | 76 | -webkit-font-smoothing: antialiased; 77 | -moz-osx-font-smoothing: grayscale; 78 | } 79 | 80 | .slick-prev 81 | { 82 | left: -25px; 83 | } 84 | [dir='rtl'] .slick-prev 85 | { 86 | right: -25px; 87 | left: auto; 88 | } 89 | .slick-prev:before 90 | { 91 | content: '←'; 92 | } 93 | [dir='rtl'] .slick-prev:before 94 | { 95 | content: '→'; 96 | } 97 | 98 | .slick-next 99 | { 100 | right: -25px; 101 | } 102 | [dir='rtl'] .slick-next 103 | { 104 | right: auto; 105 | left: -25px; 106 | } 107 | .slick-next:before 108 | { 109 | content: '→'; 110 | } 111 | [dir='rtl'] .slick-next:before 112 | { 113 | content: '←'; 114 | } 115 | 116 | /* Dots */ 117 | .slick-dotted.slick-slider 118 | { 119 | margin-bottom: 30px; 120 | } 121 | 122 | .slick-dots 123 | { 124 | position: absolute; 125 | bottom: -25px; 126 | 127 | display: block; 128 | 129 | width: 100%; 130 | padding: 0; 131 | margin: 0; 132 | 133 | list-style: none; 134 | 135 | text-align: center; 136 | } 137 | .slick-dots li 138 | { 139 | position: relative; 140 | 141 | display: inline-block; 142 | 143 | width: 20px; 144 | height: 20px; 145 | margin: 0 5px; 146 | padding: 0; 147 | 148 | cursor: pointer; 149 | } 150 | .slick-dots li button 151 | { 152 | font-size: 0; 153 | line-height: 0; 154 | 155 | display: block; 156 | 157 | width: 20px; 158 | height: 20px; 159 | padding: 5px; 160 | 161 | cursor: pointer; 162 | 163 | color: transparent; 164 | border: 0; 165 | outline: none; 166 | background: transparent; 167 | } 168 | .slick-dots li button:hover, 169 | .slick-dots li button:focus 170 | { 171 | outline: none; 172 | } 173 | .slick-dots li button:hover:before, 174 | .slick-dots li button:focus:before 175 | { 176 | opacity: 1; 177 | } 178 | .slick-dots li button:before 179 | { 180 | font-family: 'slick'; 181 | font-size: 6px; 182 | line-height: 20px; 183 | 184 | position: absolute; 185 | top: 0; 186 | left: 0; 187 | 188 | width: 20px; 189 | height: 20px; 190 | 191 | content: '•'; 192 | text-align: center; 193 | 194 | opacity: .25; 195 | color: black; 196 | 197 | -webkit-font-smoothing: antialiased; 198 | -moz-osx-font-smoothing: grayscale; 199 | } 200 | .slick-dots li.slick-active button:before 201 | { 202 | opacity: .75; 203 | color: black; 204 | } 205 | -------------------------------------------------------------------------------- /static/lib/slick/slick.css: -------------------------------------------------------------------------------- 1 | /* Slider */ 2 | .slick-slider 3 | { 4 | position: relative; 5 | 6 | display: block; 7 | box-sizing: border-box; 8 | 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | 14 | -webkit-touch-callout: none; 15 | -khtml-user-select: none; 16 | -ms-touch-action: pan-y; 17 | touch-action: pan-y; 18 | -webkit-tap-highlight-color: transparent; 19 | } 20 | 21 | .slick-list 22 | { 23 | position: relative; 24 | 25 | display: block; 26 | overflow: hidden; 27 | 28 | margin: 0; 29 | padding: 0; 30 | } 31 | .slick-list:focus 32 | { 33 | outline: none; 34 | } 35 | .slick-list.dragging 36 | { 37 | cursor: pointer; 38 | cursor: hand; 39 | } 40 | 41 | .slick-slider .slick-track, 42 | .slick-slider .slick-list 43 | { 44 | -webkit-transform: translate3d(0, 0, 0); 45 | -moz-transform: translate3d(0, 0, 0); 46 | -ms-transform: translate3d(0, 0, 0); 47 | -o-transform: translate3d(0, 0, 0); 48 | transform: translate3d(0, 0, 0); 49 | } 50 | 51 | .slick-track 52 | { 53 | position: relative; 54 | top: 0; 55 | left: 0; 56 | 57 | display: block; 58 | } 59 | .slick-track:before, 60 | .slick-track:after 61 | { 62 | display: table; 63 | 64 | content: ''; 65 | } 66 | .slick-track:after 67 | { 68 | clear: both; 69 | } 70 | .slick-loading .slick-track 71 | { 72 | visibility: hidden; 73 | } 74 | 75 | .slick-slide 76 | { 77 | display: none; 78 | float: left; 79 | 80 | height: 100%; 81 | min-height: 1px; 82 | } 83 | [dir='rtl'] .slick-slide 84 | { 85 | float: right; 86 | } 87 | .slick-slide img 88 | { 89 | display: block; 90 | } 91 | .slick-slide.slick-loading img 92 | { 93 | display: none; 94 | } 95 | .slick-slide.dragging img 96 | { 97 | pointer-events: none; 98 | } 99 | .slick-initialized .slick-slide 100 | { 101 | display: block; 102 | } 103 | .slick-loading .slick-slide 104 | { 105 | visibility: hidden; 106 | } 107 | .slick-vertical .slick-slide 108 | { 109 | display: block; 110 | 111 | height: auto; 112 | 113 | border: 1px solid transparent; 114 | } 115 | .slick-arrow.slick-hidden { 116 | display: none; 117 | } 118 | -------------------------------------------------------------------------------- /static/lib/slick/slick.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | _ _ _ _ 3 | ___| (_) ___| | __ (_)___ 4 | / __| | |/ __| |/ / | / __| 5 | \__ \ | | (__| < _ | \__ \ 6 | |___/_|_|\___|_|\_(_)/ |___/ 7 | |__/ 8 | 9 | Version: 1.6.0 10 | Author: Ken Wheeler 11 | Website: http://kenwheeler.github.io 12 | Docs: http://kenwheeler.github.io/slick 13 | Repo: http://github.com/kenwheeler/slick 14 | Issues: http://github.com/kenwheeler/slick/issues 15 | 16 | */ 17 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):"undefined"!=typeof exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){"use strict";var b=window.Slick||{};b=function(){function c(c,d){var f,e=this;e.defaults={accessibility:!0,adaptiveHeight:!1,appendArrows:a(c),appendDots:a(c),arrows:!0,asNavFor:null,prevArrow:'',nextArrow:'',autoplay:!1,autoplaySpeed:3e3,centerMode:!1,centerPadding:"50px",cssEase:"ease",customPaging:function(b,c){return a('