[MERGE] upstream

bzr revid: fme@openerp.com-20131014095642-qxl7ceqptfsj215l
This commit is contained in:
Fabien Meghazi 2013-10-14 11:56:42 +02:00
commit 631b1cbc09
73 changed files with 3609 additions and 663 deletions

View File

@ -0,0 +1,23 @@
# Spanish (Argentina) translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-10-07 21:16+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Spanish (Argentina) <es_AR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-08 05:42+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: account_accountant
#: model:ir.actions.client,name:account_accountant.action_client_account_menu
msgid "Open Accounting Menu"
msgstr ""

View File

@ -0,0 +1,23 @@
# Estonian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 14:39+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Estonian <et@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: account_cancel
#: view:account.invoice:0
msgid "Cancel"
msgstr ""

View File

@ -1,30 +1,28 @@
# Estonian translation for openobject-addons
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-03 16:03+0000\n"
"PO-Revision-Date: 2011-10-10 19:33+0000\n"
"Last-Translator: Aare Vesi <Unknown>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 14:40+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Estonian <et@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-12-04 05:53+0000\n"
"X-Generator: Launchpad (build 16335)\n"
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: base_crypt
#: model:ir.model,name:base_crypt.model_res_users
#. module: auth_crypt
#: field:res.users,password_crypt:0
msgid "Encrypted Password"
msgstr "Krüpteeritud Parool"
#. module: auth_crypt
#: model:ir.model,name:auth_crypt.model_res_users
msgid "Users"
msgstr ""
#, python-format
#~ msgid "Error"
#~ msgstr "Viga"
#~ msgid "res.users"
#~ msgstr "res.users"
msgstr "Kasutajad"

View File

@ -0,0 +1,23 @@
# Estonian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 14:34+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Estonian <et@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: auth_oauth_signup
#: model:ir.model,name:auth_oauth_signup.model_res_users
msgid "Users"
msgstr "Kasutajad"

View File

@ -0,0 +1,185 @@
# Hindi translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 05:55+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Hindi <hi@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: base_report_designer
#: model:ir.model,name:base_report_designer.model_base_report_sxw
msgid "base.report.sxw"
msgstr "base.report.sxw"
#. module: base_report_designer
#: view:base_report_designer.installer:0
msgid "OpenERP Report Designer Configuration"
msgstr ""
#. module: base_report_designer
#: view:base_report_designer.installer:0
msgid ""
"This plug-in allows you to create/modify OpenERP Reports into OpenOffice "
"Writer."
msgstr ""
#. module: base_report_designer
#: view:base.report.sxw:0
msgid "Upload the modified report"
msgstr ""
#. module: base_report_designer
#: view:base.report.file.sxw:0
msgid "The .SXW report"
msgstr ""
#. module: base_report_designer
#: model:ir.model,name:base_report_designer.model_base_report_designer_installer
msgid "base_report_designer.installer"
msgstr ""
#. module: base_report_designer
#: model:ir.model,name:base_report_designer.model_base_report_rml_save
msgid "base.report.rml.save"
msgstr ""
#. module: base_report_designer
#: view:base_report_designer.installer:0
msgid "Configure"
msgstr ""
#. module: base_report_designer
#: view:base_report_designer.installer:0
msgid "title"
msgstr ""
#. module: base_report_designer
#: field:base.report.file.sxw,report_id:0
#: field:base.report.sxw,report_id:0
msgid "Report"
msgstr ""
#. module: base_report_designer
#: view:base.report.rml.save:0
msgid "The RML Report"
msgstr ""
#. module: base_report_designer
#: model:ir.ui.menu,name:base_report_designer.menu_action_report_designer_wizard
msgid "Report Designer"
msgstr ""
#. module: base_report_designer
#: field:base_report_designer.installer,name:0
msgid "File name"
msgstr ""
#. module: base_report_designer
#: view:base.report.file.sxw:0
#: view:base.report.sxw:0
msgid "Get a report"
msgstr ""
#. module: base_report_designer
#: view:base_report_designer.installer:0
#: model:ir.actions.act_window,name:base_report_designer.action_report_designer_wizard
msgid "OpenERP Report Designer"
msgstr ""
#. module: base_report_designer
#: view:base.report.sxw:0
msgid "Continue"
msgstr ""
#. module: base_report_designer
#: field:base.report.rml.save,file_rml:0
msgid "Save As"
msgstr ""
#. module: base_report_designer
#: help:base_report_designer.installer,plugin_file:0
msgid ""
"OpenObject Report Designer plug-in file. Save as this file and install this "
"plug-in in OpenOffice."
msgstr ""
#. module: base_report_designer
#: view:base.report.rml.save:0
msgid "Save RML FIle"
msgstr ""
#. module: base_report_designer
#: field:base.report.file.sxw,file_sxw:0
#: field:base.report.file.sxw,file_sxw_upload:0
msgid "Your .SXW file"
msgstr ""
#. module: base_report_designer
#: view:base_report_designer.installer:0
msgid "Installation and Configuration Steps"
msgstr ""
#. module: base_report_designer
#: field:base_report_designer.installer,description:0
msgid "Description"
msgstr ""
#. module: base_report_designer
#: view:base.report.file.sxw:0
msgid ""
"This is the template of your requested report.\n"
"Save it as a .SXW file and open it with OpenOffice.\n"
"Don't forget to install the OpenERP SA OpenOffice package to modify it.\n"
"Once it is modified, re-upload it in OpenERP using this wizard."
msgstr ""
#. module: base_report_designer
#: model:ir.actions.act_window,name:base_report_designer.action_view_base_report_sxw
msgid "Base Report sxw"
msgstr ""
#. module: base_report_designer
#: model:ir.model,name:base_report_designer.model_base_report_file_sxw
msgid "base.report.file.sxw"
msgstr ""
#. module: base_report_designer
#: field:base_report_designer.installer,plugin_file:0
msgid "OpenObject Report Designer Plug-in"
msgstr ""
#. module: base_report_designer
#: model:ir.actions.act_window,name:base_report_designer.action_report_designer_installer
msgid "OpenERP Report Designer Installation"
msgstr ""
#. module: base_report_designer
#: view:base.report.sxw:0
msgid "Cancel"
msgstr ""
#. module: base_report_designer
#: view:base.report.sxw:0
msgid "or"
msgstr ""
#. module: base_report_designer
#: model:ir.model,name:base_report_designer.model_ir_actions_report_xml
msgid "ir.actions.report.xml"
msgstr ""
#. module: base_report_designer
#: view:base.report.sxw:0
msgid "Select your report"
msgstr ""

View File

@ -0,0 +1,33 @@
# Estonian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 14:42+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Estonian <et@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: claim_from_delivery
#: view:stock.picking.out:0
msgid "Claims"
msgstr "Nõuded"
#. module: claim_from_delivery
#: model:res.request.link,name:claim_from_delivery.request_link_claim_from_delivery
msgid "Delivery Order"
msgstr "Tarnetellimus"
#. module: claim_from_delivery
#: model:ir.actions.act_window,name:claim_from_delivery.action_claim_from_delivery
msgid "Claim From Delivery"
msgstr ""

View File

@ -0,0 +1,85 @@
# Estonian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-10-09 15:39+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Estonian <et@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
"X-Generator: Launchpad (build 16799)\n"
#. module: crm_todo
#: model:ir.model,name:crm_todo.model_project_task
msgid "Task"
msgstr "Ülesanne"
#. module: crm_todo
#: view:crm.lead:0
msgid "Timebox"
msgstr "Ajalahter"
#. module: crm_todo
#: view:crm.lead:0
msgid "Lead"
msgstr ""
#. module: crm_todo
#: view:crm.lead:0
msgid "For cancelling the task"
msgstr "Ülesande katkestamiseks"
#. module: crm_todo
#: view:crm.lead:0
msgid "Next"
msgstr "Järgmine"
#. module: crm_todo
#: model:ir.actions.act_window,name:crm_todo.crm_todo_action
#: model:ir.ui.menu,name:crm_todo.menu_crm_todo
msgid "My Tasks"
msgstr "Minu ülesanded"
#. module: crm_todo
#: view:crm.lead:0
#: field:crm.lead,task_ids:0
msgid "Tasks"
msgstr "Ülesanded"
#. module: crm_todo
#: view:crm.lead:0
msgid "Done"
msgstr "Valmis"
#. module: crm_todo
#: view:crm.lead:0
msgid "Cancel"
msgstr "Katkesta"
#. module: crm_todo
#: model:ir.model,name:crm_todo.model_crm_lead
msgid "Lead/Opportunity"
msgstr ""
#. module: crm_todo
#: field:project.task,lead_id:0
msgid "Lead / Opportunity"
msgstr ""
#. module: crm_todo
#: view:crm.lead:0
msgid "For changing to done state"
msgstr ""
#. module: crm_todo
#: view:crm.lead:0
msgid "Previous"
msgstr "Eelmine"

View File

@ -133,7 +133,7 @@ class hr_si_project(osv.osv_memory):
def check_state(self, cr, uid, ids, context=None):
obj_model = self.pool.get('ir.model.data')
emp_id = self.default_get(cr, uid, context)['emp_id']
emp_id = self.default_get(cr, uid, ['emp_id'], context)['emp_id']
# get the latest action (sign_in or out) for this employee
cr.execute('select action from hr_attendance where employee_id=%s and action in (\'sign_in\',\'sign_out\') order by name desc limit 1', (emp_id,))
res = (cr.fetchone() or ('sign_out',))[0]

View File

@ -143,7 +143,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var self = this;
this._super();
this.$el.click(function(){
self.order.selectLine(this.model);
self.order.selectLine(self.model);
self.trigger('order_line_selected');
});
if(this.model.is_selected()){

View File

@ -93,8 +93,7 @@
</div>
</div>
<div class="oe_right oe_button_box" name="buttons" groups="base.group_user">
<button name="%(act_project_project_2_project_task_all)d" string="Tasks"
type="action" attrs="{'invisible':[('use_tasks','=', 0)]}"/>
<field name="tasks" widget="x2many_counter" string="Tasks" attrs="{'invisible':[('use_tasks','=', False)]}" options='{"views": [[false, "kanban"], [false, "tree"], [false, "form"], [false, "calendar"], [false, "gantt"], [false, "graph"]]}'/>
<button name="attachment_tree_view" string="Documents" type="object"/>
</div>
<group>

2132
addons/sale/i18n/hi.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,14 +12,14 @@ OpenERP Website CMS
'depends': ['web', 'share'],
'installable': True,
'data': [
'views/views.xml',
'data/website_data.xml',
'security/ir.model.access.csv',
'views/website_templates.xml',
'views/website_views.xml',
'views/snippets.xml',
'views/themes.xml',
'website_data.xml',
'website_view.xml',
'security/ir.model.access.csv',
],
'demo': [
'website_demo.xml',
'data/website_demo.xml',
]
}

View File

@ -7,18 +7,27 @@ import json
import logging
import os
import datetime
import re
from sys import maxint
import psycopg2
import slugify
import werkzeug
import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers
from PIL import Image
try:
from slugify import slugify
except ImportError:
def slugify(s, max_length=None):
spaceless = re.sub(r'\s+', '-', s)
specialless = re.sub(r'[^-_a-z0-9]', '', spaceless)
return specialless[:max_length]
import openerp
from openerp.osv import fields
from openerp.addons.website.models import website
from openerp.addons.web import http
from openerp.addons.web.http import request
@ -53,7 +62,7 @@ class Website(openerp.addons.web.controllers.main.Home):
def pagenew(self, path, noredirect=NOPE):
module = 'website'
# completely arbitrary max_length
idname = slugify.slugify(path, max_length=50)
idname = slugify(path, max_length=50)
request.cr.execute('SAVEPOINT pagenew')
imd = request.registry['ir.model.data']
@ -251,11 +260,16 @@ class Website(openerp.addons.web.controllers.main.Home):
def publish(self, id, object):
_id = int(id)
_object = request.registry[object]
obj = _object.browse(request.cr, request.uid, _id)
values = {}
if 'website_published' in _object._all_columns:
values['website_published'] = not obj.website_published
if 'website_published_datetime' in _object._all_columns and values.get('website_published'):
values['website_published_datetime'] = fields.datetime.now()
_object.write(request.cr, request.uid, [_id],
{'website_published': not obj.website_published},
context=request.context)
values, context=request.context)
obj = _object.browse(request.cr, request.uid, _id)
return obj.website_published and True or False

View File

@ -36,17 +36,22 @@
<record id="action_website" model="ir.actions.act_url">
<field name="name">Website</field>
<field name="url">/</field>
<field name="target">self</field>
</record>
<record id="action_website_tutorial" model="ir.actions.act_url">
<field name="name">Website With Tutorial</field>
<field name="url">/?tutorial=true</field>
<field name="target">self</field>
</record>
<record id="menu_website" model="ir.ui.menu">
<field name="name">Website</field>
<field name="sequence" eval="510"/>
<field name="action" ref="action_website"/>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_website"/>
<field name="action_id" ref="action_website_tutorial"/>
<field name="state">open</field>
</record>

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
import ir_fields
import view
import ir_ui_view
import website
import ir_rule

View File

@ -144,7 +144,7 @@ class website(osv.osv):
qweb_context.update(values)
qweb_context.update(
_request=request, # begin with underscore to mark this attribute as unsafe
request=request, # TODO maybe rename to _request to mark this attribute as unsafe
json=simplejson,
website=request.website,
url_for=url_for,

View File

@ -0,0 +1,22 @@
jQuery Nearest Element plugin
======
**Full documentation is at <http://gilmoreorless.github.io/jquery-nearest/>**
**Demo:** <http://gilmoreorless.github.io/jquery-nearest/demo.html>
Method signatures:
* `$.nearest({x, y}, selector)` - find `$(selector)` closest to x/y point on screen
* `$(elem).nearest(selector)` - find `$(selector)` closest to elem
* `$(elemSet).nearest({x, y})` - filter `$(elemSet)` and return closest to x/y point on screen
Reverse logic:
* `$.furthest()`
* `$(elem).furthest()`
Intersecting/touching:
* `$.touching()`
* `$(elem).touching()`

View File

@ -0,0 +1,12 @@
{
"name": "jquery-nearest",
"version": "1.2.1",
"main": "jquery.nearest.js",
"ignore": [
"**/.*",
"test"
],
"dependencies": {
"jquery": ">=1.4"
}
}

View File

@ -0,0 +1,223 @@
/*!
* jQuery Nearest plugin v1.2.1
*
* Finds elements closest to a single point based on screen location and pixel dimensions
* http://gilmoreorless.github.com/jquery-nearest/
* Open source under the MIT licence: http://gilmoreorless.mit-license.org/2011/
*
* Requires jQuery 1.4 or above
* Also supports Ben Alman's "each2" plugin for faster looping (if available)
*/
/**
* Method signatures:
*
* $.nearest({x, y}, selector) - find $(selector) closest to point
* $(elem).nearest(selector) - find $(selector) closest to elem
* $(elemSet).nearest({x, y}) - filter $(elemSet) and return closest to point
*
* Also:
* $.furthest()
* $(elem).furthest()
*
* $.touching()
* $(elem).touching()
*/
;(function ($, undefined) {
/**
* Internal method that does the grunt work
*
* @param mixed selector Any valid jQuery selector providing elements to filter
* @param hash options Key/value list of options for matching elements
* @param mixed thisObj (optional) Any valid jQuery selector that represents self
* for the "includeSelf" option
* @return array List of matching elements, can be zero length
*/
var rPerc = /^([\d.]+)%$/;
function nearest(selector, options, thisObj) {
// Normalise selector and dimensions
selector || (selector = 'div'); // I STRONGLY recommend passing in a selector
var $container = $(options.container),
containerOffset = $container.offset() || {left: 0, top: 0},
containerDims = [
containerOffset.left + $container.width(),
containerOffset.top + $container.height()
],
percProps = {x: 0, y: 1, w: 0, h: 1},
prop, match;
for (prop in percProps) if (percProps.hasOwnProperty(prop)) {
match = rPerc.exec(options[prop]);
if (match) {
options[prop] = containerDims[percProps[prop]] * match[1] / 100;
}
}
// Get elements and work out x/y points
var $all = $(selector),
cache = [],
furthest = !!options.furthest,
checkX = !!options.checkHoriz,
checkY = !!options.checkVert,
compDist = furthest ? 0 : Infinity,
point1x = parseFloat(options.x) || 0,
point1y = parseFloat(options.y) || 0,
point2x = parseFloat(point1x + options.w) || point1x,
point2y = parseFloat(point1y + options.h) || point1y,
tolerance = options.tolerance || 0,
hasEach2 = !!$.fn.each2,
// Shortcuts to help with compression
min = Math.min,
max = Math.max;
// Normalise the remaining options
if (!options.includeSelf && thisObj) {
$all = $all.not(thisObj);
}
if (tolerance < 0) {
tolerance = 0;
}
// Loop through all elements and check their positions
$all[hasEach2 ? 'each2' : 'each'](function (i, elem) {
var $this = hasEach2 ? elem : $(this),
off = $this.offset(),
x = off.left,
y = off.top,
w = $this.outerWidth(),
h = $this.outerHeight(),
x2 = x + w,
y2 = y + h,
maxX1 = max(x, point1x),
minX2 = min(x2, point2x),
maxY1 = max(y, point1y),
minY2 = min(y2, point2y),
intersectX = minX2 >= maxX1,
intersectY = minY2 >= maxY1,
distX, distY, distT, isValid;
if (
// .nearest() / .furthest()
(checkX && checkY) ||
// .touching()
(!checkX && !checkY && intersectX && intersectY) ||
// .nearest({checkVert: false})
(checkX && intersectY) ||
// .nearest({checkHoriz: false})
(checkY && intersectX)
) {
distX = intersectX ? 0 : maxX1 - minX2;
distY = intersectY ? 0 : maxY1 - minY2;
distT = intersectX || intersectY ?
max(distX, distY) :
Math.sqrt(distX * distX + distY * distY);
isValid = furthest ?
distT >= compDist - tolerance :
distT <= compDist + tolerance;
if (isValid) {
compDist = furthest ?
max(compDist, distT) :
min(compDist, distT);
cache.push({
node: this,
dist: distT
});
}
}
});
// Make sure all cached items are within tolerance range
var len = cache.length,
filtered = [],
compMin, compMax,
i, item;
if (len) {
if (furthest) {
compMin = compDist - tolerance;
compMax = compDist;
} else {
compMin = compDist;
compMax = compDist + tolerance;
}
for (i = 0; i < len; i++) {
item = cache[i];
if (item.dist >= compMin && item.dist <= compMax) {
filtered.push(item.node);
}
}
}
return filtered;
}
$.each(['nearest', 'furthest', 'touching'], function (i, name) {
// Internal default options
// Not exposed publicly because they're method-dependent and easily overwritten anyway
var defaults = {
x: 0, // X position of top left corner of point/region
y: 0, // Y position of top left corner of point/region
w: 0, // Width of region
h: 0, // Height of region
tolerance: 1, // Distance tolerance in pixels, mainly to handle fractional pixel rounding bugs
container: document, // Container of objects for calculating %-based dimensions
furthest: name == 'furthest', // Find max distance (true) or min distance (false)
includeSelf: false, // Include 'this' in search results (t/f) - only applies to $(elem).func(selector) syntax
checkHoriz: name != 'touching', // Check variations in X axis (t/f)
checkVert: name != 'touching' // Check variations in Y axis (t/f)
};
/**
* $.nearest() / $.furthest() / $.touching()
*
* Utility functions for finding elements near a specific point or region on screen
*
* @param hash point Co-ordinates for the point or region to measure from
* "x" and "y" keys are required, "w" and "h" keys are optional
* @param mixed selector Any valid jQuery selector that provides elements to filter
* @param hash options (optional) Extra filtering options
* Not technically needed as the options could go on the point object,
* but it's good to have a consistent API
* @return jQuery object containing matching elements in selector
*/
$[name] = function (point, selector, options) {
if (!point || point.x === undefined || point.y === undefined) {
return $([]);
}
var opts = $.extend({}, defaults, point, options || {});
return $(nearest(selector, opts));
};
/**
* SIGNATURE 1:
* $(elem).nearest(selector) / $(elem).furthest(selector) / $(elem).touching(selector)
*
* Finds all elements in selector that are nearest to/furthest from elem
*
* @param mixed selector Any valid jQuery selector that provides elements to filter
* @param hash options (optional) Extra filtering options
* @return jQuery object containing matching elements in selector
*
* SIGNATURE 2:
* $(elemSet).nearest(point) / $(elemSet).furthest(point) / $(elemSet).touching(point)
*
* Filters elemSet to return only the elements nearest to/furthest from point
* Effectively a wrapper for $.nearest(point, elemSet) but with the benefits of method chaining
*
* @param hash point Co-ordinates for the point or region to measure from
* @return jQuery object containing matching elements in elemSet
*/
$.fn[name] = function (selector, options) {
var opts;
if (selector && $.isPlainObject(selector)) {
opts = $.extend({}, defaults, selector, options || {});
return this.pushStack(nearest(this, opts));
}
var offset = this.offset(),
dimensions = {
x: offset.left,
y: offset.top,
w: this.outerWidth(),
h: this.outerHeight()
};
opts = $.extend({}, defaults, dimensions, options || {});
return this.pushStack(nearest(selector, opts, this));
};
});
})(jQuery);

View File

@ -0,0 +1,11 @@
/*!
* jQuery Nearest plugin v1.2.1
*
* Finds elements closest to a single point based on screen location and pixel dimensions
* http://gilmoreorless.github.com/jquery-nearest/
* Open source under the MIT licence: http://gilmoreorless.mit-license.org/2011/
*
* Requires jQuery 1.4 or above
* Also supports Ben Alman's "each2" plugin for faster looping (if available)
*/
;(function(e,t){function r(t,r,i){t||(t="div");var s=e(r.container),o=s.offset()||{left:0,top:0},u=[o.left+s.width(),o.top+s.height()],a={x:0,y:1,w:0,h:1},f,l;for(f in a)a.hasOwnProperty(f)&&(l=n.exec(r[f]),l&&(r[f]=u[a[f]]*l[1]/100));var c=e(t),h=[],p=!!r.furthest,d=!!r.checkHoriz,v=!!r.checkVert,m=p?0:Infinity,g=parseFloat(r.x)||0,y=parseFloat(r.y)||0,b=parseFloat(g+r.w)||g,w=parseFloat(y+r.h)||y,E=r.tolerance||0,S=!!e.fn.each2,x=Math.min,T=Math.max;!r.includeSelf&&i&&(c=c.not(i)),E<0&&(E=0),c[S?"each2":"each"](function(t,n){var r=S?n:e(this),i=r.offset(),s=i.left,o=i.top,u=r.outerWidth(),a=r.outerHeight(),f=s+u,l=o+a,c=T(s,g),N=x(f,b),C=T(o,y),k=x(l,w),L=N>=c,A=k>=C,O,M,_,D;if(d&&v||!d&&!v&&L&&A||d&&A||v&&L)O=L?0:c-N,M=A?0:C-k,_=L||A?T(O,M):Math.sqrt(O*O+M*M),D=p?_>=m-E:_<=m+E,D&&(m=p?T(m,_):x(m,_),h.push({node:this,dist:_}))});var N=h.length,C=[],k,L,A,O;if(N){p?(k=m-E,L=m):(k=m,L=m+E);for(A=0;A<N;A++)O=h[A],O.dist>=k&&O.dist<=L&&C.push(O.node)}return C}var n=/^([\d.]+)%$/;e.each(["nearest","furthest","touching"],function(n,i){var s={x:0,y:0,w:0,h:0,tolerance:1,container:document,furthest:i=="furthest",includeSelf:!1,checkHoriz:i!="touching",checkVert:i!="touching"};e[i]=function(n,i,o){if(!n||n.x===t||n.y===t)return e([]);var u=e.extend({},s,n,o||{});return e(r(i,u))},e.fn[i]=function(t,n){var i;if(t&&e.isPlainObject(t))return i=e.extend({},s,t,n||{}),this.pushStack(r(this,i));var o=this.offset(),u={x:o.left,y:o.top,w:this.outerWidth(),h:this.outerHeight()};return i=e.extend({},s,u,n||{}),this.pushStack(r(t,i,this))}})})(jQuery);

View File

@ -0,0 +1,24 @@
{
"name": "nearest",
"title": "jQuery Nearest Element",
"description": "Find the elements in a page that are closest to (or furthest away from) a particular point or element, based on pixel dimensions",
"version": "1.2.1",
"keywords": ["nearest", "furthest", "touching", "pixel", "distance"],
"homepage": "http://gilmoreorless.github.io/jquery-nearest/",
"docs": "http://gilmoreorless.github.io/jquery-nearest/",
"demo": "http://gilmoreorless.github.io/jquery-nearest/demo.html",
"bugs": "https://github.com/gilmoreorless/jquery-nearest/issues",
"author": {
"name": "Gilmore Davidson",
"url": "http://shoehornwithteeth.com/"
},
"licenses": [
{
"type": "MIT",
"url": "http://gilmoreorless.mit-license.org/2011/"
}
],
"dependencies": {
"jquery": ">=1.4"
}
}

View File

@ -164,7 +164,7 @@ table.editorbar-panel td.selected {
position: fixed;
left: 0px;
right: 0px;
top: 50px;
top: 51px;
background: #282828;
-webkit-box-shadow: 0px 10px 10px -10px black inset;
-moz-box-shadow: 0px 10px 10px -10px black inset;
@ -183,9 +183,7 @@ table.editorbar-panel td.selected {
display: inline-block;
border-bottom: none !important;
vertical-align: middle;
margin-top: 5px;
min-width: 120px;
margin-left: 6px;
z-index: 1;
}
#oe_snippets .nav > li {
@ -208,11 +206,9 @@ table.editorbar-panel td.selected {
display: inline-block;
white-space: nowrap;
background: black;
margin-top: 5px;
}
#oe_snippets .tab-content > div {
background: black;
padding: 5px;
}
#oe_snippets .tab-content > div label {
width: 44px;
@ -237,8 +233,8 @@ table.editorbar-panel td.selected {
.oe_snippet {
float: left;
vertical-align: top;
width: 100px;
margin: 1px;
width: 93px;
margin-left: 1px;
margin-top: 0px;
position: relative;
overflow: hidden;
@ -269,6 +265,7 @@ table.editorbar-panel td.selected {
text-shadow: 0 0 2px black;
}
.oe_snippet .oe_snippet_thumbnail .oe_snippet_thumbnail_img {
height: 74px;
-webkit-transition: all 150ms linear;
-moz-transition: all 150ms linear;
-o-transition: all 150ms linear;

View File

@ -148,7 +148,7 @@ table.editorbar-panel
position: fixed
left: 0px
right: 0px
top: 50px
top: 51px
background: rgb(40,40,40)
+box-shadow(0px 10px 10px -10px black inset)
z-index: 1010
@ -162,9 +162,7 @@ table.editorbar-panel
display: inline-block
border-bottom: none !important
vertical-align: middle
margin-top: 5px
min-width: 120px
margin-left: 6px
> li
display: block
float: none
@ -182,10 +180,8 @@ table.editorbar-panel
display: inline-block
white-space: nowrap
background: black
margin-top: 5px
> div
background: rgb(0,0,0)
padding: 5px
label
width: 44px
color: #fff
@ -200,8 +196,8 @@ table.editorbar-panel
.oe_snippet
float: left
vertical-align: top
width: 100px
margin: 1px
width: 93px
margin-left: 1px
margin-top: 0px
position: relative
overflow: hidden
@ -223,6 +219,7 @@ table.editorbar-panel
display: block
+text-shadow(0 0 2px rgb(0,0,0))
.oe_snippet_thumbnail_img
height: 74px
@include transition(all 150ms linear)
+box-shadow(inset 0px 0px 0px 3px #333333)
@include transform( scale(1,1))

View File

@ -223,11 +223,15 @@ footer {
}
/* ---- HACK FOR COVERING UP CK EDITOR BOGUS P INSERTION --- */
.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child {
.oe_structure.oe_editable.oe_empty:empty, .oe_editable[data-oe-type=html]:empty, .oe_structure.oe_editable.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child {
background-image: url("/website/static/src/img/drag_here.png");
}
.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child {
background-image: url("/website/static/src/img/edit_here.png");
background-repeat: no-repeat;
background-position: center top;
height: 200px !important;
background-position: center;
height: 220px !important;
}
.oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child {
@ -238,8 +242,8 @@ footer {
content: "Click Edit To Create Content";
text-align: center;
display: block;
padding-top: 100px;
padding-bottom: 50px;
padding-top: 160px;
padding-bottom: 30px;
color: grey;
font-size: 24px;
}
@ -345,6 +349,17 @@ footer {
}
/* Parallax Theme */
.parallax_quote {
background: url("/website/static/src/img/parallax/quote.png") center center no-repeat fixed;
background-size: contain;
}
.parallax_quote .carousel-indicators li {
border: 1px solid grey;
}
.parallax_quote .carousel-indicators .active {
background-color: grey;
}
.parallax_full {
background: #eeeeee bottom center repeat fixed;
min-height: 650px;

View File

@ -151,11 +151,14 @@ footer
/* ---- HACK FOR COVERING UP CK EDITOR BOGUS P INSERTION --- */
.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child
.oe_structure.oe_editable.oe_empty:empty, .oe_editable[data-oe-type=html]:empty, .oe_structure.oe_editable.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child
background-image: url('/website/static/src/img/drag_here.png')
.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child
background-image: url('/website/static/src/img/edit_here.png')
background-repeat: no-repeat
background-position: center top
height: 200px !important
background-position: center
height: 220px !important
.oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child
position: static
@ -164,8 +167,8 @@ footer
content: 'Click Edit To Create Content'
text-align: center
display: block
padding-top: 100px
padding-bottom: 50px
padding-top: 160px
padding-bottom: 30px
color: grey
font-size: 24px
@ -263,6 +266,15 @@ footer
/* Parallax Theme */
.parallax_quote
background: url('/website/static/src/img/parallax/quote.png') center center no-repeat fixed
background-size: contain
.carousel-indicators
li
border: 1px solid grey
.active
background-color: grey
.parallax_full
background: #eee bottom center repeat fixed
min-height: 650px

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -313,7 +313,15 @@
},
});
},
stop: function(){
stop: function(ev, ui){
if (action === 'insert' && ! dropped) {
var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first()
if (el) {
el.after($toInsert)
dropped = true;
}
}
$('.oe_drop_zone').droppable('destroy').remove();
if (dropped) {
var $target = false;
@ -1134,33 +1142,6 @@
},
});
website.snippet.animationRegistry.surprise = website.snippet.Animation.extend({
start: function() {
this._super();
var hue=0;
var beat = false;
var self = this;
self.$target.append('<iframe width="1px" height="1px" src="http://www.youtube.com/embed/WY24YNsOefk?autoplay=1" frameborder="0"></iframe>');
var a = setInterval(function(){
self.$target.next().css({'-webkit-filter':'hue-rotate('+hue+'deg)'});
self.$target.prev().css({'-webkit-filter':'hue-rotate('+(-hue)+'deg)'});
hue -= 5;
}, 10);
setTimeout(function(){
clearInterval(a);
setInterval(function(){
var filter = 'hue-rotate('+hue+'deg)'+ (beat ? ' invert()' : '');
$(document.documentElement).css({'-webkit-filter': filter}); hue += 5;
if(hue % 35 === 0){
beat = !beat;
}
}, 10);
},5000);
}
});
/*
* data-snippet-id automatically setted
* Don't need to add data-snippet-id="..." into the views

View File

@ -19,9 +19,24 @@
keyboard: false,
});
this.tour.addSteps(_.map(this.steps, function (step) {
step.title = render('website.tour_title', { title: step.title });
step.title = render('website.tour_popover_title', { title: step.title });
return step;
}));
this.monkeyPatchTour();
},
monkeyPatchTour: function () {
var self = this;
// showStep should wait for 'element' to appear instead of moving to the next step
self.tour.showStep = function (i) {
var step = self.tour.getStep(i);
return (function proceed () {
if (step.orphan || $(step.element).length > 0) {
return Tour.prototype.showStep.call(self.tour, i);
} else {
setTimeout(proceed, 50);
}
}());
};
},
reset: function () {
this.tourStorage.removeItem(this.id+'_current_step');
@ -68,7 +83,7 @@
website.EditorBasicTour = website.EditorTour.extend({
id: 'add_banner_tour',
name: "How to add a banner",
name: "Insert a banner",
init: function (editor) {
var self = this;
self.steps = [
@ -77,43 +92,44 @@
orphan: true,
backdrop: true,
title: "Welcome to your website!",
content: "This tutorial will guide you through the firsts steps to build your enterprise class website.",
template: render('website.tour_full', { next: "Continue", end: "Close Tutorial" }),
content: "This tutorial will guide you to build your first page. We will start by adding a banner.",
template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }),
},
{
stepId: 'edit-page',
element: 'button[data-action=edit]',
placement: 'right',
placement: 'bottom',
reflex: true,
title: "Edit this page",
content: "Every page of your website can be edited. Click the <em>Edit</em> button to modify your homepage.",
template: render('website.tour_simple'),
},
{
stepId: 'add-banner',
orphan: true,
backdrop: true,
title: "Now, let's add a banner",
content: "Let's add a banner on the top of the page to make your homepage more attractive.",
template: render('website.tour_confirm', { next: "Continue" }),
content: "Every page of your website can be modified through the <i>Edit</i> button.",
template: render('website.tour_popover'),
},
{
stepId: 'add-block',
element: 'button[data-action=snippet]',
placement: 'right',
reflex: true,
title: "Shows Building Blocks",
content: "Click on the <em>Insert Blocks</em> button to show the available building blocks.",
template: render('website.tour_simple'),
placement: 'bottom',
title: "Insert building blocks",
content: "To add content in a page, you can insert building blocks.",
template: render('website.tour_popover'),
onShow: function () {
function refreshAddBlockStep () {
self.tour.showStep(self.indexOfStep('add-block'));
editor.off('rte:ready', editor, refreshAddBlockStep);
}
editor.on('rte:ready', editor, refreshAddBlockStep);
$('button[data-action=snippet]').click(function () {
self.movetoStep('drag-banner');
});
}
},
{
stepId: 'drag-banner',
element: '#website-top-navbar [data-snippet-id=carousel]',
placement: 'bottom',
title: "Drag & Drop a Banner",
content: "Drag the <em>Banner</em> block and drop it to the top of your page (purple zone).",
template: render('website.tour_simple'),
onShown: function () {
content: "Drag the <em>Banner</em> block and drop it in your page. <p class='text-muted'>Tip: release the mouse button when you are in a valid zone, with a preview of the banner.</p>",
template: render('website.tour_popover'),
onShow: function () {
function beginDrag () {
$('.popover.tour').remove();
$('body').off('mousedown', beginDrag);
@ -130,10 +146,10 @@
{
stepId: 'edit-title',
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
placement: 'top',
title: "Set your Banner text",
content: "Click on the text to start modifying it then click <em>Continue</em> to continue the tutorial.<br>You can also use the options of the top menubar to change the look of the banner. ",
template: render('website.tour_confirm', { next: "Continue" }),
placement: 'left',
title: "Customize banner's text",
content: "Click in the text and start editing it. Click continue once it's done.",
template: render('website.tour_popover', { next: "Continue" }),
onHide: function () {
var $banner = $("#wrap [data-snippet-id=carousel]:first");
if ($banner.length) {
@ -145,9 +161,9 @@
stepId: 'customize-banner',
element: '.oe_overlay_options .oe_options',
placement: 'left',
title: "Customize your new Banner style",
content: "Click on <em>Customize</em> and change the background of your banner.<br>If your are satisfied with the current background, just click <em>Continue</em>.",
template: render('website.tour_confirm', { next: "Continue" }),
title: "Customize the banner",
content: "You can customize components of your page through the <em>Customize</em> menu. Try to change the background of your banner.",
template: render('website.tour_popover', { next: "Continue" }),
onShow: function () {
$('.dropdown-menu [name=carousel-background]').click(function () {
self.movetoStep('save-changes');
@ -160,8 +176,8 @@
placement: 'right',
reflex: true,
title: "Save your modifications",
content: "Click the <em>Save</em> button to apply modifications on your website.",
template: render('website.tour_simple'),
content: "Once you click on save, your website page is updated.",
template: render('website.tour_popover'),
onHide: function () {
self.saveStep('part-2');
},
@ -170,40 +186,17 @@
{
stepId: 'part-2',
orphan: true,
backdrop: true,
title: "Congratutaltions!",
content: "Congratulations on your first modifications.",
template: render('website.tour_confirm', { next: "OK" }),
},
{
stepId: 'show-customize',
element: '#customize-menu-button',
placement: 'left',
title: "Customize your Website",
content: "Go on the <em>Customize</em> menu to get access customization options for the current page.",
template: render('website.tour_simple'),
onShow: function () {
editor.on('rte:customize_menu_ready', self, function () {
self.movetoStep('change-themes');
});
},
},
{
stepId: 'change-themes',
element: '.js_change_theme',
placement: 'left',
reflex: true,
title: "Click to View Themes",
content: "You can change the theme at any time to customize the look and feel of your website.",
template: render('website.tour_simple'),
title: "Congratulation!",
content: "Your homepage have been updated. Now, we suggest you to insert others building blocks like texts and images to structure your page.",
template: render('website.tour_popover', { next: "Continue" }),
},
{
stepId: 'show-tutorials',
element: '#help-menu-button',
placement: 'left',
title: "Help is always available",
content: "You can find more tutorials in the <em>Help</em> menu.",
template: render('website.tour_end', { end: "Close" }),
content: "But you can always click here if you want more tutorials.",
template: render('website.tour_popover', { end: "Close" }),
},
];
return this._super();
@ -249,7 +242,7 @@
menu.append($menuItem);
});
var url = new website.UrlParser(window.location.href);
if (url.search === '?tutorial=true' || website.tutorials.basic.startOfPart2()) {
if (url.search.indexOf('?tutorial=true') === 0 || website.tutorials.basic.startOfPart2()) {
website.tutorials.basic.start();
}
$('.tour-backdrop').click(function (e) {
@ -260,4 +253,4 @@
},
});
}());
}());

View File

@ -55,7 +55,8 @@
New page
</label>
</h3>
<input type="text" class="form-control pages url-source" id="link-new"/>
<input type="text" class="form-control pages url-source"
id="link-new" placeholder="Page Name"/>
</li>
<li class="list-group-item form-group clearfix">
<div class="pull-right">

View File

@ -1,44 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.tour_simple">
<t t-name="website.tour_popover">
<div class="popover tour">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div class="popover-content"></div>
<t t-if="next or end">
<nav class="popover-navigation">
<t t-if="next">
<button class="btn btn-sm btn-default" data-role="next"><t t-esc="next"/></button>
</t>
<small t-if="next &amp;&amp; end">
<span class="text-muted"> or </span>
<button class="btn-link" data-role="end" style="float: none; padding: 0"><t t-esc="end"/></button>
</small>
<t t-if="end &amp;&amp; ! next">
<button class="btn btn-sm btn-default" data-role="end"><t t-esc="end"/></button>
</t>
</nav>
</t>
</div>
</t>
<t t-name="website.tour_confirm">
<div class="popover tour">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div class="popover-content"></div>
<nav class="popover-navigation">
<button class="btn btn-sm btn-default" data-role="next"><t t-esc="next"/></button>
</nav>
</div>
</t>
<t t-name="website.tour_end">
<div class="popover tour">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div class="popover-content"></div>
<nav class="popover-navigation">
<button class="btn btn-sm btn-default" data-role="end"><t t-esc="end"/></button>
</nav>
</div>
</t>
<t t-name="website.tour_full">
<div class="popover tour">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div class="popover-content"></div>
<nav class="popover-navigation">
<button class="btn btn-sm btn-default" data-role="next"><t t-esc="next"/></button>
<button class="btn btn-sm btn-default" data-role="end"><t t-esc="end"/></button>
</nav>
</div>
</t>
<t t-name="website.tour_title">
<t t-name="website.tour_popover_title">
<t t-esc="title"/><button title="Close Tutorial" type="button" class="close" data-role="end">×</button>
</t>
</templates>
</templates>

View File

@ -40,8 +40,7 @@
<li data-value="/website/static/src/img/banner/velour.jpg"><a>Velour</a></li>
<li data-value="/website/static/src/img/banner/wood.jpg"><a>Wood</a></li>
<li data-value="/website/static/src/img/banner/yellow_green.jpg"><a>Yellow Green</a></li>
<li class="divider"></li>
<li class="oe_custom_bg"><a><b>Chose your picture</b></a></li>
<li class="oe_custom_bg"><a><b>Choose a photo...</b></a></li>
</ul>
</li>
<li class="oe_snippet_options dropdown-submenu">
@ -149,7 +148,7 @@
<div data-snippet-id="jumbotron" data-selector-children=".oe_structure, [data-oe-type=html]">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_jumbotron.png"/>
<span class="oe_snippet_thumbnail_title">Jumbotron</span>
<span class="oe_snippet_thumbnail_title">Big Message</span>
</div>
<section class="oe_snippet_body jumbotron mt16 mb16">
<div class="container">
@ -215,7 +214,7 @@
<div class="row">
<div class="col-md-12">
<h1 class="text-center">Your Website Title</h1>
<h3 class="text-muted text-center">Ans a great subtitle too</h3>
<h3 class="text-muted text-center">And a great subtitle too</h3>
</div>
</div>
</div>
@ -365,15 +364,6 @@
</div>
</div>
<div data-snippet-id="surprise" data-selector-children=".oe_structure">
<div class="oe_snippet_body" style="height: 0; position: absolute;"></div>
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_surprise.png"/>
<span class="oe_snippet_thumbnail_title">Surprise!</span>
</div>
</div>
</div>
<div id="snippet_feature" class="tab-pane fade">
@ -724,6 +714,76 @@
</section>
</div>
<div data-snippet-id="parallax_quote" data-selector-children=".oe_structure, [data-oe-type=html]">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_parallax.png"/>
<span class="oe_snippet_thumbnail_title">Quotes Slider</span>
</div>
<section class="oe_snippet_body parallax_quote oe_structure">
<div id="myQuoteCarousel" class="carousel slide oe_medium mb32" data-snippet-id="carousel">
<!-- Indicators -->
<ol class="carousel-indicators mb0">
<li data-target="#myQuoteCarousel" data-slide-to="0" class="active"></li>
<li data-target="#myQuoteCarousel" data-slide-to="1"></li>
</ol>
<div class="carousel-inner">
<div class="item text_only active">
<div class="container">
<div class="content">
<div class="row">
<div class="col-md-5 col-md-offset-4 mt64">
<blockquote>
<p>
Write here a quote from one of your customer. Quotes are are
great way to give confidence in your products or services.
</p>
<small>Author of this quote</small>
</blockquote>
</div>
</div>
</div>
<div class="carousel-image hidden-xs">
<img src="/website/static/src/img/banner/banner_picture.png" alt="Banner OpenERP Image"/>
</div>
</div>
</div>
<div class="item text_only">
<div class="container">
<div class="content">
<div class="row">
<div class="col-md-5 col-md-offset-4 mt64">
<blockquote>
<p>
OpenERP provides essential platform for our project management.
Things are better organized and more visible with it.
</p>
<small>John Doe, CEO</small>
</blockquote>
</div>
</div>
</div>
<div class="carousel-image hidden-xs">
<img src="/website/static/src/img/banner/banner_picture.png" alt="Banner OpenERP Image"/>
</div>
</div>
</div>
</div>
<a class="carousel-control left hidden" href="#myQuoteCarousel" data-slide="prev" style="width: 10%"><span class="glyphicon glyphicon-chevron-left"><span class="hidden">.</span></span></a>
<a class="carousel-control right hidden" href="#myQuoteCarousel" data-slide="next" style="width: 10%"><span class="glyphicon glyphicon-chevron-right"><span class="hidden">.</span></span></a>
</div>
</section>
</div>
</div>
<div id="snippet_hidden" class="hidden">
@ -735,7 +795,7 @@
<div id="snippet_styles" class="hidden">
<div data-snippet-id='darken' data-selector='section'>
<div class='oe_snippet_label'>Darken</div>
<div class='oe_snippet_label'>Enlight Background</div>
<div class='oe_snippet_class'>dark</div>
</div>

View File

@ -3,9 +3,8 @@
-->
<openerp>
<data>
<!--
Files used in the generic theme, mostly bootstrap and a few OpenERP tags
-->
<!-- Layout and generic templates -->
<template id="website.theme" name="Theme">
<link id="bootstrap_css" rel='stylesheet' href='/website/static/lib/bootstrap/css/bootstrap.css' t-ignore="true"/>
<link id="website_css" rel='stylesheet' href='/website/static/src/css/website.css' t-ignore="true"/>
@ -54,6 +53,7 @@
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
<script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/MutationObserver.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
@ -89,7 +89,6 @@
<li t-if="request.multilang and
(len(website.language_ids) &gt; 1 or editable)" class="dropdown">
<!-- TODO: use flags for language selection -->
<t t-set="lang_selected" t-value="website.lang_selected"/>
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<t t-esc="lang_selected[0]['name']"/> <span class="caret"></span>
</a>
@ -101,13 +100,14 @@
<t t-esc="lg.name"/>
</a>
</li>
<li t-if="editable" class="text-center">
<li t-if="editable" class="divider"/>
<li t-if="editable">
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
<t t-if="request.httprequest.query_string">
<t t-set="url_return" t-value="url_return + '?' + request.httprequest.query_string"/>
</t>
<a t-attf-href="/web#action=base.action_view_base_language_install&amp;url_return=#{url_return}">
Add a Language
Add a language...
</a>
</li>
</ul>
@ -209,19 +209,107 @@
</xpath>
</template>
<template id="homepage" name="Homepage" page="True">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"></div>
<template id="publish_management">
<t t-if="editable" t-ignore="true">
<div class="pull-right btn-group">
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name">
<a t-attf-class="btn btn-xs btn-#{object.id and object.website_published and 'success' or 'default'}" t-att-id="'dopprod-%s' % object.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % object.id">
<t t-raw="0"/>
<li>
<a href="#" class="js_publish_btn css_unpublish">Unpublish</a>
<a href="#" class="js_publish_btn css_publish">Publish</a>
</li>
<li t-if="publish_duplicate">
<a t-att-href="publish_duplicate">Duplicate</a>
</li>
</ul>
</div>
<t t-if="publish_edit">
<a class="btn btn-xs btn-default" title="Edit in backend" t-att-href="'/admin/#model=%s&amp;id=%s' % (object._name, object.id)"><span t-attf-class="glyphicon glyphicon-wrench"></span></a>
</t>
</div>
</t>
</template>
<template id="publish_short">
<t t-if="editable" t-ignore="true">
<a href="#" t-att-data-id="object.id" t-att-data-object="object._name"
t-att-data-publish="object.id and object.website_published and 'on' or 'off'"
class="pull-right js_publish">
<span t-attf-class="text-success css_publish glyphicon glyphicon-ok"></span>
<span t-attf-class="text-danger css_unpublish glyphicon glyphicon-remove-circle"></span>
<span t-attf-class="text-muted css_published glyphicon glyphicon-ok"></span>
<span t-attf-class="text-muted css_unpublished glyphicon glyphicon-remove-circle"></span>
</a>
</t>
</template>
<template id="pager">
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination">
<li t-att-class=" 'disabled' if pager['page']['num'] == 1 else '' ">
<a t-att-href=" pager['page_start']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
</li>
<t t-foreach="pager['pages']" t-as="page">
<li t-att-class=" 'active' if page['num'] == pager['page']['num'] else '' "> <a t-att-href="page['url']" t-raw="page['num']"></a></li>
</t>
<li t-att-class=" 'disabled' if pager['page']['num'] == pager['page_count'] else '' ">
<a t-att-href=" pager['page_end']['url'] if pager['page']['num'] != pager['page_count'] else '' ">Next</a>
</li>
</ul>
</template>
<template id="kanban">
<t t-set="step"><t t-esc="step or 0"/></t>
<t t-set="scope"><t t-esc="scope or 0"/></t>
<t t-set="orderby"><t t-esc="orderby or 'name'"/></t>
<t t-raw="website.kanban(model, domain, column, template, step=step, scope=scope, orderby=orderby)"/>
</template>
<template id="kanban_contain">
<table class="table js_kanban">
<thead>
<tr>
<t t-set="width" t-value="str(round(100.0 / len(objects), 2)) + '%'"/>
<t t-foreach="objects">
<th t-att-width="width">
<div t-field="column_id.name" class="text-center"></div>
</th>
</t>
</tr>
</thead>
<tbody>
<tr>
<t t-foreach="objects">
<td class="js_kanban_col" t-att-data-template="template" t-att-data-domain="domain" t-att-data-page_count="page_count" t-att-data-model="model" t-att-data-step="step" t-att-data-orderby="orderby">
<t t-foreach="object_ids" t-as="object_id">
<t t-call="#{ template }"></t>
</t>
<!-- pager -->
<div t-if="1 != page_end" class="pagination pagination-centered">
<ul>
<li t-attf-class="prev #{'active' if page == 1 else '' }"> <a t-att-href=" '%s,%s-%s' % (kanban_url, column_id.id, (page &gt; 1 and page-1 or 1)) ">Prev</a></li>
<t t-foreach="range(page_start, page_end+1)" t-as="p">
<li t-att-class=" 'active' if page == p else '' "> <a t-att-href=" '%s,%s-%s' % (kanban_url, column_id.id, p)" t-esc="p"></a></li>
</t>
<li t-attf-class="next #{'active' if page == page_end else '' }"> <a t-att-href=" '%s,%s-%s' % (kanban_url, column_id.id, (page &lt; page_end and page+1 or page_end) )">Next</a></li>
</ul>
</div>
</td>
</t>
</tr>
</tbody>
</table>
</template>
<!-- Error and special pages -->
<template id="default_page">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"></div>
</t>
</template>
<template id="404">
<t t-call="website.layout">
<div id="wrap">
@ -286,6 +374,28 @@
</t>
</template>
<template id="robots">
# robotstxt.org/
User-agent: *
Sitemap: <t t-esc="url_root"/>sitemap.xml
</template>
<template id="sitemap">
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<t t-foreach="pages" t-as="page">
<url t-esc="page['url']"/>
</t>
</urlset>
</template>
<!-- Actual pages -->
<template id="homepage" name="Homepage" page="True">
<t t-call="website.layout">
<div id="wrap" class="oe_structure oe_empty"></div>
</t>
</template>
<template id="company_description" name="Company Description">
<address>
<strong t-field="res_company.name">Name</strong><br />
@ -317,7 +427,7 @@
<a t-attf-href="mailto:{{ res_company.email }}" class="btn btn-primary">Send us an email</a>
</div>
</div>
<div class="col-md-4">
<div class="col-md-4 mb32">
<t t-call="website.company_description"/>
</div>
</div>
@ -357,106 +467,5 @@
</t>
</template>
<template id="pager">
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination">
<li t-att-class=" 'disabled' if pager['page']['num'] == 1 else '' ">
<a t-att-href=" pager['page_start']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
</li>
<t t-foreach="pager['pages']" t-as="page">
<li t-att-class=" 'active' if page['num'] == pager['page']['num'] else '' "> <a t-att-href="page['url']" t-raw="page['num']"></a></li>
</t>
<li t-att-class=" 'disabled' if pager['page']['num'] == pager['page_count'] else '' ">
<a t-att-href=" pager['page_end']['url'] if pager['page']['num'] != pager['page_count'] else '' ">Next</a>
</li>
</ul>
</template>
<template id="publish_management">
<t t-if="editable" t-ignore="true">
<div class="pull-right btn-group">
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name">
<a t-attf-class="btn btn-xs btn-#{object.id and object.website_published and 'success' or 'default'}" t-att-id="'dopprod-%s' % object.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % object.id">
<t t-raw="0"/>
<li>
<a href="#" class="js_publish_btn css_unpublish">Unpublish</a>
<a href="#" class="js_publish_btn css_publish">Publish</a>
</li>
</ul>
</div>
<t t-if="publish_edit">
<a class="btn btn-xs btn-default" title="Edit in backend" t-att-href="'/admin/#model=%s&amp;id=%s' % (object._name, object.id)"><span t-attf-class="glyphicon glyphicon-wrench"></span></a>
</t>
</div>
</t>
</template>
<template id="publish_short">
<t t-if="editable" t-ignore="true">
<a href="#" t-att-data-id="object.id" t-att-data-object="object._name"
t-att-data-publish="object.id and object.website_published and 'on' or 'off'"
class="pull-right js_publish">
<span t-attf-class="text-success css_publish glyphicon glyphicon-ok"></span>
<span t-attf-class="text-danger css_unpublish glyphicon glyphicon-remove-circle"></span>
<span t-attf-class="text-muted css_published glyphicon glyphicon-ok"></span>
<span t-attf-class="text-muted css_unpublished glyphicon glyphicon-remove-circle"></span>
</a>
</t>
</template>
<template id="kanban">
<t t-set="step"><t t-esc="step or 0"/></t>
<t t-set="scope"><t t-esc="scope or 0"/></t>
<t t-set="orderby"><t t-esc="orderby or 'name'"/></t>
<t t-raw="website.kanban(model, domain, column, template, step=step, scope=scope, orderby=orderby)"/>
</template>
<template id="kanban_contain">
<table class="table js_kanban">
<thead>
<tr>
<t t-set="width" t-value="str(round(100.0 / len(objects), 2)) + '%'"/>
<t t-foreach="objects">
<th t-att-width="width">
<div t-field="column_id.name" class="text-center"></div>
</th>
</t>
</tr>
</thead>
<tbody>
<tr>
<t t-foreach="objects">
<td class="js_kanban_col" t-att-data-template="template" t-att-data-domain="domain" t-att-data-page_count="page_count" t-att-data-model="model" t-att-data-step="step" t-att-data-orderby="orderby">
<t t-foreach="object_ids" t-as="object_id">
<t t-call="#{ template }"></t>
</t>
<!-- pager -->
<div t-if="1 != page_end" class="pagination pagination-centered">
<ul>
<li t-attf-class="prev #{'active' if page == 1 else '' }"> <a t-att-href=" '%s,%s-%s' % (kanban_url, column_id.id, (page &gt; 1 and page-1 or 1)) ">Prev</a></li>
<t t-foreach="range(page_start, page_end+1)" t-as="p">
<li t-att-class=" 'active' if page == p else '' "> <a t-att-href=" '%s,%s-%s' % (kanban_url, column_id.id, p)" t-esc="p"></a></li>
</t>
<li t-attf-class="next #{'active' if page == page_end else '' }"> <a t-att-href=" '%s,%s-%s' % (kanban_url, column_id.id, (page &lt; page_end and page+1 or page_end) )">Next</a></li>
</ul>
</div>
</td>
</t>
</tr>
</tbody>
</table>
</template>
<template id="robots">
# robotstxt.org/
User-agent: *
Sitemap: <t t-esc="url_root"/>sitemap.xml
</template>
<template id="sitemap">
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<t t-foreach="pages" t-as="page">
<url t-esc="page['url']"/>
</t>
</urlset>
</template>
</data>
</openerp>

View File

@ -20,5 +20,5 @@
##############################################################################
import controllers
import website_blog
import models
import wizard

View File

@ -32,19 +32,19 @@ OpenERP Blog
'author': 'OpenERP SA',
'depends': ['knowledge', 'website_mail'],
'data': [
'website_blog_data.xml',
'views/website_blog_classic.xml',
'data/website_blog_data.xml',
'views/website_blog_views.xml',
'views/website_blog_templates.xml',
# 'wizard/document_page_create_menu_view.xml',
'wizard/document_page_show_diff_view.xml',
'security/ir.model.access.csv',
'security/website_mail.xml',
'security/website_blog.xml',
],
'demo': [
'website_blog_demo.xml'
'data/website_blog_demo.xml'
],
'test': [
'test/document_page_test00.yml'
'tests/test_website_blog.yml'
],
'qweb': [
'static/src/xml/*.xml'

View File

@ -35,10 +35,10 @@ class WebsiteBlog(http.Controller):
@website.route([
'/blog/',
'/blog/<int:category_id>/',
'/blog/<int:category_id>/<int:blog_post_id>/',
'/blog/<int:category_id>/page/<int:page>/',
'/blog/<int:category_id>/<int:blog_post_id>/page/<int:page>/',
'/blog/<int:blog_post_id>/',
'/blog/<int:blog_post_id>/page/<int:page>/',
'/blog/cat/<int:category_id>/',
'/blog/cat/<int:category_id>/page/<int:page>/',
'/blog/tag/',
'/blog/tag/<int:tag_id>/',
], type='http', auth="public")
@ -90,11 +90,13 @@ class WebsiteBlog(http.Controller):
tag = tag_obj.browse(cr, uid, tag_id, context=context)
if category_id:
category = category_obj.browse(cr, uid, category_id, context=context)
if category and blog_post_id:
elif blog_post_id:
blog_post = blog_post_obj.browse(cr, uid, blog_post_id, context=context)
blog_message_ids = blog_post.website_message_ids
else:
category = blog_post.category_id
category_id = category.id
if not blog_post_id:
if category and tag:
blog_posts = [cat_post for cat_post in category.blog_post_ids
if tag_id in [post_tag.id for post_tag in cat_post.tag_ids]]
@ -108,7 +110,7 @@ class WebsiteBlog(http.Controller):
if blog_posts:
pager = request.website.pager(
url="/blog/%s/" % category_id,
url="/blog/cat/%s/" % category_id,
total=len(blog_posts),
page=page,
step=self._category_post_per_page,
@ -120,7 +122,7 @@ class WebsiteBlog(http.Controller):
if blog_post:
pager = request.website.pager(
url="/blog/%s/%s/" % (category_id, blog_post_id),
url="/blog/%s/" % blog_post_id,
total=len(blog_message_ids),
page=page,
step=self._post_comment_per_page,
@ -138,8 +140,8 @@ class WebsiteBlog(http.Controller):
nav[year]['months'].append(group)
values = {
'categories': categories,
'category': category,
'categories': categories,
'tag': tag,
'blog_post': blog_post,
'blog_posts': blog_posts,
@ -170,13 +172,13 @@ class WebsiteBlog(http.Controller):
]
return simplejson.dumps(blog_post_data)
@website.route(['/blog/<int:category_id>/<int:blog_post_id>/post'], type='http', auth="public")
def blog_comment(self, category_id=None, blog_post_id=None, **post):
@website.route(['/blog/<int:blog_post_id>/post'], type='http', auth="public")
def blog_post_comment(self, blog_post_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
url = request.httprequest.host_url
request.session.body = post.get('body')
if request.context['is_public_user']: # purpose of this ?
return '%s/admin#action=redirect&url=%s/blog/%s/%s/post' % (url, url, category_id, blog_post_id)
return '%s/admin#action=redirect&url=%s/blog/%s/post' % (url, url, blog_post_id)
if request.session.get('body') and blog_post_id:
request.registry['blog.post'].message_post(
@ -187,17 +189,24 @@ class WebsiteBlog(http.Controller):
context=dict(context, mail_create_nosubcribe=True))
request.session.body = False
return werkzeug.utils.redirect("/blog/%s/%s/?unable_editor=1" % (category_id, blog_post_id))
return werkzeug.utils.redirect("/blog/%s/?unable_editor=1" % (blog_post_id))
@website.route(['/blog/<int:category_id>/new'], type='http', auth="public")
def create_blog_post(self, category_id=None, **post):
def blog_post_create(self, category_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
create_context = dict(context, mail_create_nosubscribe=True)
blog_post_id = request.registry['blog.post'].create(
new_blog_post_id = request.registry['blog.post'].create(
request.cr, request.uid, {
'category_id': category_id,
'name': _("Blog title"),
'content': '',
'website_published': False,
}, context=create_context)
return werkzeug.utils.redirect("/blog/%s/%s/?unable_editor=1" % (category_id, blog_post_id))
return werkzeug.utils.redirect("/blog/%s/?unable_editor=1" % (new_blog_post_id))
@website.route(['/blog/<int:blog_post_id>/duplicate'], type='http', auth="public")
def blog_post_copy(self, blog_post_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
create_context = dict(context, mail_create_nosubscribe=True)
new_blog_post_id = request.registry['blog.post'].copy(cr, uid, blog_post_id, {}, context=create_context)
return werkzeug.utils.redirect("/blog/%s/?unable_editor=1" % (new_blog_post_id))

View File

@ -3,6 +3,7 @@
<!-- <data noupdate="1"> -->
<data>
<!-- jump to blog at install -->
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Blogs</field>
<field name="target">self</field>
@ -17,15 +18,6 @@
<record id="blog_category_1" model="blog.category">
<field name="name">News</field>
<field name="description">Presentation of new OpenERP features</field>
<field name="template">
Summary of the feature
Long explanation
Conclusion
Additional ressources
</field>
</record>
<!-- Post-related subtypes for messaging / Chatter -->
@ -41,6 +33,7 @@ Additional ressources
<field name="default" eval="False"/>
<field name="description">Post Published</field>
</record>
<!-- Project-related subtypes for messaging / Chatter -->
<record id="mt_blog_category_post_new" model="mail.message.subtype">
<field name="name">New Post</field>
@ -51,20 +44,11 @@ Additional ressources
</record>
<!-- Mail group for the company's jobs -->
<record id="blog_category_2" model="blog.category">
<field name="name">Jobs</field>
<field name="description">Job Announces</field>
</record>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_option_id="website.layout" name="Job Announces">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/blog/%(website_blog.blog_category_2)d/">Jobs</a></li>
</xpath>
</template>
<!-- Mail group for the company's jobs -->
<record id="blog_category_2" model="blog.category">
<field name="name">Jobs</field>
<field name="description">Job Announces</field>
</record>
</data>

View File

@ -8,6 +8,12 @@
<field name="name">functional</field>
</record>
<record id="blog_tag_2" model="blog.tag">
<field name="name">technical</field>
</record>
<record id="blog_tag_3" model="blog.tag">
<field name="name">website</field>
</record>
<record id="blog_tag_4" model="blog.tag">
<field name="name">pos</field>
</record>
@ -16,6 +22,7 @@
<field name="name">OpenERP v8 New Features</field>
<field name="category_id" ref="blog_category_1"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_1')])]"/>
<field name="website_published" eval="True"/>
<field name="content"><![CDATA[
<section data-snippet-id='image-text'>
<div class="container">
@ -264,6 +271,7 @@ Think of it as an out-of-the-box solution to boost your business' productivity.
<field name="name">Announcig a New Partnership</field>
<field name="category_id" ref="blog_category_1"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_1')])]"/>
<field name="website_published" eval="True"/>
<field name="content"><![CDATA[
<section data-snippet-id='image-text'>
<div class="container">
@ -291,7 +299,7 @@ Think of it as an out-of-the-box solution to boost your business' productivity.
<div class="row">
<div class="col-md-12 text-center mt16 mb32">
<h2>
Linked with Project Management
OpenERP Project Management
</h2>
<h3 class="text-muted">Infinitely flexible. Incredibly easy to use.</h3>
</div>
@ -313,33 +321,6 @@ Think of it as an out-of-the-box solution to boost your business' productivity.
</div>
</div>
</section>
<section class="dark mt16 mb16" data-snippet-id='big-picture'>
<div class="container">
<div class="row">
<div class="col-md-12 text-center mt32 mb32">
<h2>Work with the hardware you already have...</h2>
</div>
<div class="col-md-12">
<img class="img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
</div>
<div class="col-md-6 col-md-offset-3 mb16 mt16">
<p class="text-center">
<b>No installation required</b>
</p>
<p class="text-center">
OpenERP's Point of Sale introduces a super clean
interface with no installation required that runs
online and offline on modern hardware. Laptops,
tablets, industrial POS, it runs on everything.
</p>
<p class="text-center">
<a href="/page/website.contactus">Get more information »</a>
</p>
</div>
</div>
</div>
</section>
]]>
</field>
</record>

View File

@ -0,0 +1 @@
import website_blog

View File

@ -37,7 +37,6 @@ class BlogCategory(osv.Model):
_columns = {
'name': fields.char('Name', required=True),
'description': fields.text('Description'),
'template': fields.html('Template'),
'blog_post_ids': fields.one2many(
'blog.post', 'category_id',
'Blogs',
@ -62,7 +61,7 @@ class BlogPost(osv.Model):
_name = "blog.post"
_description = "Blog Post"
_inherit = ['mail.thread']
_order = 'name'
_order = 'write_date DESC'
# maximum number of characters to display in summary
_shorten_max_char = 250
@ -170,6 +169,16 @@ class BlogPost(osv.Model):
self.create_history(cr, uid, ids, vals, context)
return result
def copy(self, cr, uid, id, default=None, context=None):
if default is None:
default = {}
default.update({
'website_message_ids': [],
'website_published': False,
'website_published_datetime': False,
})
return super(BlogPost, self).copy(cr, uid, id, default=default, context=context)
def img(self, cr, uid, ids, field='image_small', context=None):
post = self.browse(cr, SUPERUSER_ID, ids[0], context=context)
return "/website/image?model=%s&field=%s&id=%s" % ('res.users', field, post.create_uid.id)
@ -177,7 +186,7 @@ class BlogPost(osv.Model):
class BlogPostHistory(osv.Model):
_name = "blog.post.history"
_description = "Document Page History"
_description = "Blog Post History"
_order = 'id DESC'
_rec_name = "create_date"

View File

@ -19,11 +19,11 @@
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.addons.mail.tests.common import TestMail
from openerp.tools import mute_logger, email_split
class TestControllers(TestMailBase):
class TestControllers(TestMail):
def test_00(self):
cr, uid = self.cr, self.uid

View File

@ -1,29 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- Layout add nav and footer -->
<template id="header_footer_custom" inherit_id="website.layout">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a href="/blog/%(website_blog.blog_category_1)d/">News</a></li>
<li><a t-attf-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/blog/%(website_blog.blog_category_1)d/">News</a></li>
<li><a t-attf-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
</template>
<template id="footer_custom" inherit_option_id="website.layout" name="Job Announces">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/blog/%(website_blog.blog_category_2)d/">Jobs</a></li>
</xpath>
</template>
<!-- Blog Post Summary -->
<template id="view_blog_post_short" name="Blog Post Summary">
<template id="blog_post_short" name="Blog Post Summary">
<div>
<div class="pull-right">
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
</t>
</div>
<h2 class="text-center">
<a t-attf-href="/blog/#{blog_post.category_id.id}/#{blog_post.id}" t-field="blog_post.name"></a>
<a t-attf-href="/blog/#{blog_post.id}" t-field="blog_post.name"></a>
</h2>
<p class="post-meta text-muted text-center">
<p class="post-meta text-muted text-center" name='blog_post_data'>
<span class="icon-calendar"> <span t-field="blog_post.create_date"/></span> &amp;nbsp;
<span class="icon-user"> By <span t-field="blog_post.create_uid"/> &amp;nbsp;</span>
<span t-if="len(blog_post.message_ids) &gt; 0" class="icon-comment"> With
<a t-if="len(blog_post.message_ids) &lt;= 1" t-attf-href="/blog/#{blog_post.category_id.id}/#{blog_post.id}#comments"><t t-esc="len(blog_post.message_ids)"/> comment</a>
<a t-if="len(blog_post.message_ids) > 1" t-attf-href="/blog/#{blog_post.category_id.id}/#{blog_post.id}#comments"><t t-esc="len(blog_post.message_ids)"/> comments</a>
<span t-if="len(blog_post.message_ids) &gt; 0" class="icon-comment">
<a t-attf-href="/blog/#{blog_post.id}/#comment">
<t t-if="len(blog_post.message_ids) &lt;= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
</a>
</span>
</p>
<div t-field="blog_post.shortened_content" class="blog_content"/>
@ -33,43 +47,56 @@
</template>
<!-- Options: Blog Post Summary: hide author -->
<template id="view_blog_post_short_author" inherit_option_id="website_blog.view_blog_post_short" name="Hide Authors">
<template id="opt_blog_post_short_author" name="Hide Authors"
inherit_option_id="website_blog.blog_post_short">
<xpath expr="//span[@class='icon-user']" position="attributes">
<attribute name="class">hidden</attribute>
</xpath>
</template>
<!-- Options: Blog Post Summary: show category -->
<template id="view_blog_post_short_category" inherit_option_id="website_blog.view_blog_post_short" name="Show Blog Name">
<template id="opt_blog_post_short_category" name="Blog Name"
inherit_option_id="website_blog.blog_post_short">
<xpath expr="//span[@class='icon-user']" position="after">
<span class="icon-folder-open"> In <span t-field="blog_post.category_id"/> &amp;nbsp;</span>
</xpath>
</template>
<!-- Option: Blog Post Summary: show tags -->
<template id="opt_blog_post_short_tags" name="Tags"
inherit_option_id="website_blog.blog_post_short" inherit_id="website_blog.blog_post_short">
<xpath expr="//p[@name='blog_post_data']" position="after">
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="icon-tags"/>
<t t-foreach="blog_post.tag_ids" t-as="tag">
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
</t>
</p>
</xpath>
</template>
<!-- Blog Post Complete -->
<template id="view_blog_post" name="Blog Post">
<template id="blog_post_complete" name="Blog Post">
<div>
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
</t>
</div><div class="clearfix"/>
</div>
<div class="clearfix"/>
<h1 class="text-center" t-field="blog_post.name"/>
<p class="post-meta text-muted text-center">
<p class="post-meta text-muted text-center" name="blog_post_data">
<span class="icon-calendar"> <span t-field="blog_post.create_date"/></span> &amp;nbsp;
<span class="icon-user"> By <span t-field="blog_post.create_uid"/> &amp;nbsp;</span>
<span t-if="len(blog_post.message_ids) &gt; 0" class="icon-comment"> With
<a t-if="len(blog_post.message_ids) &lt;= 1" t-attf-href="#comments"><t t-esc="len(blog_post.message_ids)"/> comment</a>
<a t-if="len(blog_post.message_ids) > 1" t-attf-href="#comments"><t t-esc="len(blog_post.message_ids)"/> comments</a>
<a t-attf-href="#comments">
<t t-if="len(blog_post.message_ids) &lt;= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
</a>
</span>
</p>
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="icon-tags"/>
<t t-foreach="blog_post.tag_ids" t-as="tag">
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
</t>
</p>
<div t-field="blog_post.content" class="mt16"/>
<hr class="mb32"/>
@ -102,77 +129,102 @@
</template>
<!-- Options: Blog Post: user can reply -->
<template id="view_blog_post_author" inherit_option_id="website_blog.view_blog_post" name="Hide Authors">
<template id="opt_blog_post_complete_comment" name="Comment Form"
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete">
<xpath expr="//ul[last()]" position="after">
<section groups="group_website_blog_reply" class="mb32">
<h4>Leave a Comment</h4>
<form id="comment" t-attf-action="/blog/#{blog_post.category_id.id}/#{blog_post.id}/post#post"
method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-danger mt8">Post</button>
</div>
</form>
</section>
<section groups="group_website_blog_reply" class="mb32">
<h4>Leave a Comment</h4>
<form id="comment" t-attf-action="/blog/#{blog_post.id}/post#post"
method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-danger mt8">Post</button>
</div>
</form>
</section>
</xpath>
</template>
<!-- Options: Blog Post: hide author -->
<template id="view_blog_post_author" inherit_option_id="website_blog.view_blog_post" name="Hide Authors">
<template id="opt_blog_post_complete_author" name="Hide Authors"
inherit_option_id="website_blog.blog_post_complete">
<xpath expr="//span[@class='icon-user']" position="attributes">
<attribute name="class">hidden</attribute>
</xpath>
</template>
<!-- Options: Blog Post: show category -->
<template id="view_blog_post_show_category" inherit_option_id="website_blog.view_blog_post" name="Show Blog Name">
<template id="opt_blog_post_complete_category" name="Blog Name"
inherit_option_id="website_blog.blog_post_complete">
<xpath expr="//span[@class='icon-user']" position="after">
<span class="icon-folder-open"> In <span t-field="blog_post.category_id"/> &amp;nbsp;</span>
</xpath>
</template>
<!-- Options: Blog Post: show tags -->
<template id="opt_blog_post_complete_tags" name="Tags"
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete">
<xpath expr="//p[@name='blog_post_data']" position="after">
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="icon-tags"/>
<t t-foreach="blog_post.tag_ids" t-as="tag">
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
</t>
</p>
</xpath>
</template>
<!-- Page -->
<template id="index" name="Blog" page="True">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"></script>
<link rel='stylesheet' href='/website_blog/static/src/css/website_blog.css'/>
</t>
<div id="wrap">
<div class="container mt16 js_website_blog">
<div class="row">
<div class="col-lg-8 col-sm-9" t-if="not blog_post">
<t t-foreach="blog_posts" t-as="blog_post" data-publish="">
<t t-call="website_blog.view_blog_post_short"/>
</t>
<div class="pull-right" t-call="website.pager"/>
</div>
<div class="col-md-9 col-lg-8" t-if="blog_post">
<t t-call="website_blog.view_blog_post">
<t t-set="blog_post" t-value="blog_post"/>
</t>
<t t-set="head">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"></script>
<link rel='stylesheet' href='/website_blog/static/src/css/website_blog.css'/>
</t>
<div id="wrap">
<div class="container mt16 js_website_blog">
<div class="row">
<div class="col-lg-12 col-sm-12" t-if="not blog_post" id="blog_post">
<t t-if="category and editable">
<div class="row">
<a t-attf-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
</div>
</t>
<t t-foreach="blog_posts" t-as="blog_post" data-publish="">
<t t-call="website_blog.blog_post_short"/>
</t>
<div class="pull-right" t-call="website.pager"/>
</div>
<div class="col-lg-12 col-sm-12" t-if="blog_post" id="blog_posts_summary">
<t t-call="website_blog.blog_post_complete">
<t t-set="blog_post" t-value="blog_post"/>
</t>
</div>
</div>
</div>
</div>
</div>
</t>
</template>
<!-- Option: right column -->
<template id="blog_right_column" inherit_id="website_blog.index" inherit_option_id="website_blog.index" name="Right Column">
<!-- Option: Blog: Right Column -->
<template id="blog_right_column" name="Right Column"
inherit_option_id="website_blog.index" inherit_id="website_blog.index">
<xpath expr="//div[@class='row']" position="inside">
<div class="col-sm-3 hidden-xs col-lg-offset-1 mb32" id="right_column">
<t t-if="category and editable">
<a t-attf-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
</t>
<div class="clearfix mb48"/>
<div class="col-lg-3 col-sm-3 hidden-xs col-lg-offset-1 mb32" id="right_column">
</div>
</xpath>
<xpath expr="//div[@id='blog_post']" position="attributes">
<attribute name="class">col-lg-8 col-sm-8</attribute>
</xpath>
<xpath expr="//div[@id='blog_posts_summary']" position="attributes">
<attribute name="class">col-lg-8 col-sm-8</attribute>
</xpath>
</template>
<!-- Option: Right Column: archives -->
<template id="blog_history" inherit_id="website_blog.blog_right_column" inherit_option_id="website_blog.blog_right_column" name="Archives">
<template id="opt_blog_rc_history" name="Archives"
inherit_option_id="website_blog.blog_right_column" inherit_id="website_blog.blog_right_column">
<xpath expr="//div[@id='right_column']" position="inside">
<section>
<h4>Archives</h4>
@ -194,7 +246,8 @@
</template>
<!-- Option: Right Column: about us -->
<template id="blog_aboutus" inherit_option_id="website_blog.blog_right_column" inherit_id="website_blog.blog_right_column" name="About" priority="4">
<template id="opt_blog_rc_about_us" name="About Us" priority="4"
inherit_option_id="website_blog.blog_right_column" inherit_id="website_blog.blog_right_column">
<xpath expr="//div[@id='right_column']" position="inside">
<section class="mb16">
<h4>About us</h4>
@ -210,7 +263,8 @@
</template>
<!-- Option: Right Column: follow us -->
<template id="blog_followus" inherit_option_id="website_blog.blog_right_column" inherit_id="website_blog.blog_right_column" name="Follow us" priority="2">
<template id="opt_blog_rc_follow_us" name="Follow us" priority="2"
inherit_option_id="website_blog.blog_right_column" inherit_id="website_blog.blog_right_column">
<xpath expr="//div[@id='right_column']" position="inside">
<section class="mb16">
<h4>Follow us</h4>
@ -238,8 +292,9 @@
</xpath>
</template>
<!-- Option: Right Column: categories + category in blog post -->
<template id="blog_categories" inherit_option_id="website_blog.blog_right_column" name="Other Blogs" priority="6">
<!-- Option: Right Column: categories -->
<template id="opt_blog_rc_categories" name="Other Blogs" priority="6"
inherit_option_id="website_blog.blog_right_column">
<xpath expr="//div[@id='right_column']" position="inside">
<section class="mb16">
<h4>Other Blogs</h4>
@ -256,5 +311,5 @@
</xpath>
</template>
</data>
</data>
</openerp>

View File

@ -2,7 +2,7 @@
<openerp>
<data>
<menuitem name="Knowledge" id="knowledge.menu_document"/>
<menuitem name="Pages" id="menu_wiki" parent="knowledge.menu_document" sequence="20" />
<menuitem name="Blog Posts" id="menu_wiki" parent="knowledge.menu_document" sequence="20" />
<!-- Category views -->
<record model="ir.ui.view" id="view_blog_category_list">
@ -23,7 +23,6 @@
<group>
<field name="name"/>
<field name="description"/>
<field name="template" widget="html"/>
</group>
</sheet>
<div class="oe_chatter">
@ -60,14 +59,12 @@
<group>
<field name="category_id" string="Category"/>
</group>
<group>
<field name="write_uid" groups="base.group_no_one" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_document_user']}"/>
<field name="write_date" groups="base.group_no_one"/>
<field name="menu_id" groups="base.group_no_one"/>
<field name="content" placeholder="e.g. Once upon a time..." widget="html"/>
<group string="Technical" groups="base.group_no_one">
<field name="write_uid" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_document_user']}"/>
<field name="write_date"/>
<field name="menu_id"/>
</group>
<div class="oe_document_page">
<field name="content" placeholder="e.g. Once upon a time..." widget="html"/>
</div>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
@ -107,7 +104,7 @@
</p>
</field>
</record>
<menuitem id="menu_page" parent="menu_wiki" name="Pages" action="action_blog_post" sequence="10"/>
<menuitem id="menu_page" parent="menu_wiki" name="Blog Posts" action="action_blog_post" sequence="10"/>
<record model="ir.actions.act_window" id="action_blog_category">
<field name="name">Category</field>

View File

@ -20,4 +20,4 @@
##############################################################################
import controllers
import event
import models

View File

@ -13,13 +13,14 @@ Online Events
'author': 'OpenERP SA',
'depends': ['website', 'website_mail', 'event_sale', 'website_sale'],
'data': [
'event_data.xml',
'data/event_data.xml',
'views/website_event.xml',
'security/ir.model.access.csv',
'security/website_event.xml',
'event_demo.xml',
],
'qweb': ['static/src/xml/*.xml'],
'demo': [],
'demo': [
'data/event_demo.xml'
],
'installable': True,
}

View File

@ -0,0 +1 @@
import event

View File

@ -19,11 +19,11 @@
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.addons.mail.tests.common import TestMail
from openerp.tools import mute_logger, email_split
class TestControllers(TestMailBase):
class TestControllers(TestMail):
def test_00(self):
cr, uid = self.cr, self.uid

View File

@ -20,5 +20,4 @@
##############################################################################
import controllers
import mail_message
import mail_thread
import models

View File

@ -0,0 +1,2 @@
import mail_message
import mail_thread

View File

@ -19,12 +19,12 @@
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.addons.mail.tests.common import TestMail
from openerp.addons.website_mail.controllers.main import WebsiteMail
from openerp.tools import mute_logger, email_split
class TestControllers(TestMailBase):
class TestControllers(TestMail):
def test_00_subscribe(self):
# from openerp.addons.web.http import request

View File

@ -3,15 +3,18 @@
<data>
<template id="follow">
<a href="#" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'" class="pull-right js_follow" t-if="editable" t-ignore="true">
<span t-attf-class="label label-success css_follow">Follow</span>
<span t-attf-class="label label-danger css_unfollow">Unfollow</span>
<span t-attf-class="label label-success css_followed">Following</span>
<span t-attf-class="label label-danger css_unfollowed">Not Following</span>
</a>
<input type="email" name="email" class="js_follow_email css_unfollowed_email"
t-att-value="email"
t-att-placeholder="email or 'Email Address'"/>
<div name="follow">
<a href="#" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
class="pull-right js_follow" t-if="editable" t-ignore="true">
<span t-attf-class="label label-success css_follow">Follow</span>
<span t-attf-class="label label-danger css_unfollow">Unfollow</span>
<span t-attf-class="label label-success css_followed">Following</span>
<span t-attf-class="label label-danger css_unfollowed">Not Following</span>
</a>
<input type="email" name="email" class="js_follow_email css_unfollowed_email"
t-att-value="email"
t-att-placeholder="email or 'Email Address'"/>
</div>
</template>
<template id="website.layout" inherit_id="website.layout">

View File

@ -4,6 +4,9 @@ import openerp
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp.tools.translate import _
import urllib
class WebsiteMembership(http.Controller):
@ -19,21 +22,43 @@ class WebsiteMembership(http.Controller):
cr, uid, context = request.cr, request.uid, request.context
product_obj = request.registry['product.product']
membership_line_obj = request.registry['membership.membership_line']
post_name = post.get('search', '')
partner_obj = request.registry['res.partner']
post_name = post.get('name', '')
post_country_id = int(post.get('country_id', '0'))
# format displayed membership lines domain
# base domain for groupby / searches
if request.context['is_public_user']:
line_domain = [('partner.website_published', '=', True)]
base_line_domain = [('partner.website_published', '=', True)]
else:
line_domain = [(1, '=', 1)]
base_line_domain = [(1, '=', 1)]
if membership_id:
line_domain += [('membership_id', '=', membership_id)]
base_line_domain += [('membership_id', '=', membership_id)]
membership = product_obj.browse(cr, openerp.SUPERUSER_ID, membership_id, context=context)
else:
membership = ''
if post_name:
line_domain += ['|', ('partner.name', 'ilike', "%%%s%%" % post_name), ('partner.website_description', 'ilike', "%%%s%%" % post_name)]
base_line_domain += ['|', ('partner.name', 'ilike', "%%%s%%" % post_name), ('partner.website_description', 'ilike', "%%%s%%" % post_name)]
# group by country, based on all customers (base domain)
membership_line_ids = membership_line_obj.search(cr, uid, base_line_domain, context=context)
countries = partner_obj.read_group(
cr, uid, [('member_lines', 'in', membership_line_ids)], ["id", "country_id"],
groupby="country_id", orderby="country_id", context=request.context)
countries_total = sum([country_dict['country_id_count'] for country_dict in countries])
countries.insert(0, {
'country_id_count': countries_total,
'country_id': (0, _("All Countries"))
})
# displayed membership lines
line_domain = list(base_line_domain)
if post_country_id:
line_domain += [('partner.country_id', '=', post_country_id)]
membership_line_ids = membership_line_obj.search(cr, uid, line_domain, context=context)
membership_lines = membership_line_obj.browse(cr, uid, membership_line_ids, context=context)
google_map_partner_ids = ",".join([str(m.partner.id) for m in membership_lines])
partner_ids = [m.partner and m.partner.id for m in membership_lines]
google_map_partner_ids = ",".join([str(pid) for pid in partner_ids])
# format domain for group_by and memberships
membership_domain = [('membership', '=', True)]
@ -44,11 +69,14 @@ class WebsiteMembership(http.Controller):
pager = request.website.pager(url="/members/", total=len(membership_line_ids), page=page, step=self._references_per_page, scope=7, url_args=post)
values = {
'membership_line_ids': membership_lines,
'membership_ids': memberships,
'membership_lines': membership_lines,
'memberships': memberships,
'membership': membership,
'countries': countries,
'google_map_partner_ids': google_map_partner_ids,
'pager': pager,
'name_search': post_name,
'post': post,
'search': "?%s" % urllib.urlencode(post),
}
return request.website.render("website_membership.index", values)

View File

@ -32,44 +32,47 @@
Looking for a partner ?<br/>
<small>Find an association member</small>
</h1>
<div class="col-md-4" id="membership_left">
<h3>Associations</h3>
<ul class="nav">
<t t-foreach="membership_ids" t-as="membership_id">
<li><a t-href="/members/association/#{ membership_id.id }"><t t-esc="membership_id.name"/></a></li>
<div class="col-md-4" id="left_column">
<ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>Associations</h3></li>
<li t-att-class="membership and '' or 'active'"><a t-href="/members/">All</a></li>
<t t-foreach="memberships" t-as="membership_id">
<li t-att-class="membership and membership_id.id == membership.id and 'active' or ''">
<a t-href="/members/association/#{ membership_id.id }"><t t-esc="membership_id.name"/></a>
</li>
</t>
</ul>
</div>
<div class="col-md-8" id="ref_content">
<div class='navbar'>
<div>
<t t-call="website.pager">
<t t-set="classname">pull-left</t>
</t>
<form t-action="/members/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">
<input type="text" name="search" class="search-query col-md-2 mt4 form-control" placeholder="Search" t-att-value="name_search"/>
</div>
</form>
</div>
<t t-call="website.pager">
<t t-set="classname">pull-left</t>
</t>
<form t-action="/members/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">
<input type="text" name="name" class="search-query col-md-2 mt4 form-control" placeholder="Search" t-att-value="post.get('name', '')"/>
</div>
</form>
</div>
<div>
<t t-if="not membership_line_ids">
<t t-if="not membership_lines">
<p>No result found.</p>
</t>
<t t-foreach="membership_line_ids" t-as="membership_line_id">
<t t-if="membership_id != membership_line_id.membership_id.id">
<t t-set="membership_id" t-value="membership_line_id.membership_id.id"/>
<t t-foreach="membership_lines" t-as="membership_line_id">
<t t-if="current_membership_id != membership_line_id.membership_id.id">
<t t-set="current_membership_id" t-value="membership_line_id.membership_id.id"/>
<h3 class="text-center"><span t-field="membership_line_id.membership_id"/></h3>
</t>
<t t-set="partner" t-value="membership_line_id.partner"/>
<div class="media thumbnail" data-publish="">
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
<div class="media" data-publish="">
<t t-call="website.publish_management">
<t t-set="object" t-value="partner"/>
</t>
<a class="pull-left" t-href="/members/#{ partner.id }/">
<img class="media-object" t-att-src="partner.img('image_small')"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-href="/members/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a> - <span t-field="membership_line_id.membership_id"/>
<a class="media-heading" t-href="/members/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<div t-field="partner.website_short_description"/>
</div>
</div>
@ -80,15 +83,34 @@
</t>
</template>
<template id="ref_country" inherit_id="website_membership.index" inherit_option_id="website_membership.index" name="Left World Map">
<xpath expr="//div[@id='membership_left']/ul" position="after">
<div>
<h3>World Map</h3>
<!-- Option: index: Left Google Map -->
<template id="opt_index_country" name="Location"
inherit_option_id="website_membership.index" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[last()]" position="after">
<ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>Location</h3></li>
<t t-foreach="countries">
<li t-if="country_id" t-att-class="post.get('country_id', '0') == str(country_id and country_id[0]) and 'active' or ''">
<a t-href="/members/#{ search }&amp;country_id=#{ country_id[0] }"><t t-esc="country_id[1]"/>
<span class="badge pull-right"><t t-esc="country_id_count"/></span>
</a>
</li>
</t>
</ul>
</xpath>
</template>
<!-- Option: index: Left Google Map -->
<template id="opt_index_google_map" name="Left World Map"
inherit_option_id="website_membership.index" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[last()]" position="after">
<ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>World Map</h3></li>
<ul class="nav">
<iframe t-attf-src="/google_map/?width=320&amp;height=240&amp;partner_ids=#{ google_map_partner_ids }&amp;partner_url=/members/"
style="width:320px; height:260px; border:0; padding:0; margin:0;"></iframe>
</ul>
</div>
</ul>
</xpath>
</template>

View File

@ -1,4 +1,2 @@
import controllers
import website_styles
import product
import website_sale
import models

View File

@ -0,0 +1,3 @@
import website_styles
import product
import website_sale

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

View File

@ -338,15 +338,16 @@
<br/>
</t>
<div class="product_price">
<b>
<div class="product_price mt16">
<h4>
<b><span class="oe_price" t-esc="product.product_variant_ids[0].price" /></b>
<t t-if="product.product_variant_ids[0].lst_price != product.product_variant_ids[0].price">
<span class="text-danger" style="text-decoration: line-through;">
&amp;nbsp;&amp;nbsp;<small class="text-danger" style="text-decoration: line-through;">
<t t-esc="product.product_variant_ids[0].lst_price" />
</span>&amp;nbsp;
</small>
</t>
<h4><b><span class="oe_price" t-esc="product.product_variant_ids[0].price" /></b></h4>
</b>
</h4>
</div>
<button class="btn btn-primary btn-lg mt8">Add to Cart</button>
<hr t-if="product.description_sale"/>

View File

@ -33,7 +33,7 @@
</div>
</div>
</section>
<section data-snippet-id="image-text" class="mt16 mb16">
<section data-snippet-id="image-text" class="mt16">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1">
@ -64,16 +64,34 @@
<section data-snippet-id="text-image" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1 mt64">
<h3>Why you'll love an Bose Mini Bluetooth Speaker.</h3>
<div class="col-md-5 col-md-offset-1 mt32">
<h3>Bose Mini Bluetooth Speaker.</h3>
<p>
The Bose® SoundLink® mini is Bose's smallest portable Bluetooth speaker. Its ultra-compact size fits in the
palm of your hand, yet gives you full, natural sound wirelessly from your iPhone, iPad, or iPod. Grab it and go
full-featured. It can do just about everything and
experience music just about anywhere.
</p>
<div class="panel panel-default mt48" data-snippet-id="panel">
<div class="panel-heading">
<h3 class="panel-title">Characteristics</h3>
</div>
<div class="panel-body">
<ul>
<li>Sleek, compact design</li>
<li>Efficient, high-quality audio</li>
<li>Remote control for power, volume, track seek</li>
<li>Auxiliary input for portable devices</li>
<li>Universal iPod docking station fits most iPod/iPhone models</li>
<li>Charges iPod/iPhone</li>
<li>Volume control on main system</li>
</ul>
</div>
</div>
</div>
<div class="col-md-5 mt16 mb16">
<div class="col-md-4 mt16 mb16 col-md-offset1">
<img class="img img-responsive" src="/website_sale/static/src/img/play_where_you_play.jpg"/>
</div>
</div>
@ -82,7 +100,7 @@
<section data-snippet-id="text-image" class="mt16 mb16 dark">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1">
<div class="col-md-5 col-md-offset-1 mt16">
<h2>Plays where you play</h2>
<p>
The SoundLink® Mini speaker is small and light enough
@ -94,13 +112,13 @@
<p>
The rechargeable lithium-ion battery delivers up to seven hours of playtime.
And at home, you can listen even longer—the charging cradle lets
you listen while it charges. An empty battery usually fully recharges in three hours.
you listen while it charges.
</p>
</div>
<div class="col-md-5 col-md-offset-1 mt32">
<img class="img img-responsive" src="/website_sale/static/src/img/bluetooth.jpg"/>
<img class="img img-responsive" src="/website_sale/static/src/img/more_features.png"/>
</div>
<div class="col-md-5 col-md-offset-1">
<div class="col-md-5 col-md-offset-4 text-center mb16 mt16">
<h3>Bluetooth connectivity</h3>
<p>
The speaker has a range of about 30 feet, so you can enjoy
@ -116,26 +134,26 @@
<section data-snippet-id="text-image" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1 mt64 mb16">
<img class="img img-responsive" src="/website_sale/static/src/img/more_features.png"/>
<div class="col-md-5 col-md-offset-1 mt32 mb16">
<img class="img img-responsive" src="/website_sale/static/src/img/bluetooth.jpg"/>
</div>
<div class="col-md-5 mt32">
<div class="col-md-5 mt16">
<h2>More features.</h2>
<p>
Charging cradle recharges the battery and serves as a convenient
home base for your speaker, and it lets you play while it charges
Charging cradle recharges the battery and serves as a convenient
home base for your speaker, and it lets you play while it charges.
</p>
<p>
Wall charger can be plugged into the cradle or directly into the speaker
Wall charger can be plugged into the cradle or directly into the speaker
</p>
<p>
Auxiliary port lets you connect other audio sources, like an MP3 player
Auxiliary port lets you connect other audio sources, like an MP3 player
</p>
<p>
USB port allows for software update to ensure ongoing Bluetooth device compatibility
USB port allows for software update to ensure ongoing Bluetooth device compatibility
</p>
<p>
Soft covers are available separately in blue, green or orange. Pick a color to match your style.
Soft covers are available separately in blue, green or orange. Pick a color to match your style.
</p>
</div>
</div>
@ -176,13 +194,13 @@ iOS7
</div>
</div>
</section>
<section data-snippet-id="image-text" class="mt16 mb16">
<section data-snippet-id="image-text" class="mt16">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1">
<img class="img img-responsive" src="/website_sale/static/src/img/mighty.png"/>
</div>
<div class="col-md-6 mt64">
<div class="col-md-6 mt32">
<h3>Beautiful 7.9inch display.</h3>
<h4 class="small text-muted"> A screen worthy of iPad.</h4>
<p>
@ -195,20 +213,20 @@ iOS7
</div>
</div>
</section>
<section data-snippet-id="text-image" class="mt16 mb16 dark">
<section data-snippet-id="text-image" class="mb16 dark">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1 mt64">
<div class="col-md-5 col-md-offset-1 mt32">
<h2>Over 375,000 apps.</h2>
<h3 class="text-muted">If it's made for iPad, it's made for iPad mini.</h3>
<p>
Right from the start, apps made for iPad also work with iPad mini.
Theyre immersive, full-screen apps that let you do almost anything
you can imagine. And with automatic updates,
you're always getting the best experience possible.
Right from the start, apps made for iPad also work with iPad mini.
Theyre immersive, full-screen apps that let you do almost anything
you can imagine. And with automatic updates,
you're always getting the best experience possible.
</p>
</div>
<div class="col-md-5 mt16 mb16">
<div class="col-md-5 mt16">
<img class="img img-responsive" src="/website_sale/static/src/img/apps.png"/>
</div>
</div>
@ -260,79 +278,54 @@ iOS7
<record id="product.product_product_7" model="product.product">
<field name="website_published" eval="True"/>
<field name="description_sale">Technical Specifications:
Frequency response: 5Hz to 21kHz
Impedance (at 100Hz): 23 ohms
Sensitivity (at 100Hz): 109 dB SPL/mW
Drivers: Custom two-way balanced armature (woofer and tweeter in each earpiece)
Length and weight:
Cable length: 1065 mm from audio jack to splitter; 330 mm to earpiece
Weight: 0.4 ounce (10.2 grams)
Connector:
Four-conductor 3.5-mm audio jack
<field name="description_sale">Frequency: 5Hz to 21kHz
Impedance: 23 ohms
Sensitivity: 109 dB SPL/mW
Drivers: two-way balanced armature
Cable length: 1065 mm
Weight: 0.4 ounce
</field>
<field name="website_description" type="html">
<section data-snippet-id="text-image" class="mt16 mb16">
<section data-snippet-id="text-image" class="mt32">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<h1>Apple In-Ear Headphones</h1>
<div class="col-md-8 col-md-offset-2 text-center">
<h3>Two is better than one.</h3>
<p>
Unlike many small headphones, each earpiece of the Apple In-Ear Headphones
contains two separate high-performance drivers — a woofer to handle bass and
mid-range sounds and a tweeter for high-frequency audio. These dedicated
drivers help ensure accurate, detailed sound across the entire sonic spectrum.
The result: youre immersed in the music and hear details you never knew existed.
Even when listening to an old favorite, you may feel like youre hearing it for the first time.
</p>
</div>
<div class="col-md-8 col-md-offset-2 dark">
<div>
<h2>Turns out you havent heard everything.</h2>
<p>
The Apple In-Ear Headphones with Remote and Mic are engineered
for superior acoustic accuracy, balance, and clarity. So you hear details
youve been missing with other, lesser headphones.
And the biggest breakthrough? Theyre just $79.
</p>
</div>
<div>
<h3 class="mt16">Two is better than one.</h3>
<p>
Unlike many small headphones, each earpiece of the Apple In-Ear Headphones
contains two separate high-performance drivers — a woofer to handle bass and
mid-range sounds and a tweeter for high-frequency audio. These dedicated
drivers help ensure accurate, detailed sound across the entire sonic spectrum.
The result: youre immersed in the music and hear details you never knew existed.
Even when listening to an old favorite, you may feel like youre hearing it for the first time.
</p>
</div>
<div>
<div class="col-md-2">
<img class="img img-responsive" src="/website_sale/static/src/img/promo_headphones.png"/>
</div>
<div class="col-md-6">
<h3 class="mt16">Hear, hear.</h3>
<p>
The Apple In-Ear Headphones deliver a truly immersive sound experience by drastically
reducing unwanted outside noises. The soft, silicone ear tips fit snugly and comfortably
in your ear, creating a seal that isolates your music from your surroundings.
Three different sizes of ear tips are included so you can find a perfect fit for each ear.
Also included are a convenient carrying case for the ear tips and a cable-control case
for the headphones themselves.
</p>
</div>
</div>
<div class="mt16">
<div class="col-md-6">
<h3 class="mt16">Keep it clean.</h3>
<p>
Inside each earpiece is a stainless steel mesh cap that protects the precision acoustic
components from dust and debris. You can remove the caps for cleaning or replace
them with an extra set thats included in the box.
</p>
</div>
<div class="col-md-2">
<img class="img img-responsive" src="/website_sale/static/src/img/buds_closeup.png"/>
</div>
</div>
</div><div class="row mt16">
<div class="col-md-2 mt16">
<img class="img img-responsive" src="/website_sale/static/src/img/promo_headphones.png"/>
</div>
<div class="col-md-6">
<h3 class="mt16">Hear, hear.</h3>
<p>
The Apple In-Ear Headphones deliver a truly immersive sound experience by drastically
reducing unwanted outside noises. The soft, silicone ear tips fit snugly and comfortably
in your ear, creating a seal that isolates your music from your surroundings.
Three different sizes of ear tips are included so you can find a perfect fit for each ear.
Also included are a convenient carrying case for the ear tips and a cable-control case
for the headphones themselves.
</p>
</div>
<div class="col-md-4">
<h3 class="mt16">Keep it clean.</h3>
<p>
Inside each earpiece is a stainless steel mesh cap that protects the precision acoustic
components from dust and debris. You can remove the caps for cleaning or replace
them with an extra set thats included in the box.
</p>
</div>
</div><div class="row">
<div class="col-md-2 col-md-offset-5 mt16">
<img class="img img-responsive" src="/website_sale/static/src/img/buds_closeup.png"/>
</div>
</div>
</div>
@ -342,41 +335,40 @@ Four-conductor 3.5-mm audio jack
<record id="product.product_product_8" model="product.product">
<field name="website_published" eval="True"/>
<field name="description_sale">
2.7GHz quad-core Intel Core i5
<field name="description_sale">2.7GHz quad-core Intel Core i5
Turbo Boost up to 3.2GHz
8GB (two 4GB) memory
1TB hard drive1
Intel Iris Pro graphics
</field>
<field name="website_description" type="html">
<section data-snippet-id="image-text" class="mt16 mb16">
<section data-snippet-id="image-text">
<div class="container">
<div class="row">
<div class="col-md-5 mt32 col-md-offset-1">
<h3>Ultrathin design</h3>
<h3 class="text-muted">The desktop. In its most advanced form ever</h3>
<p>
Creating such a stunningly thin design took some equally stunning feats of technological innovation. We refined,re-imagined,or re-engineered everything about iMac from the inside out. The result is an advanced, elegant all-in-one computer thats as much a work of art as it is state of the art.
Creating such a stunningly thin design took some equally stunning feats of technological innovation. We refined,re-imagined,or re-engineered everything about iMac from the inside out. The result is an advanced, elegant all-in-one computer thats as much a work of art as it is state of the art.
</p>
</div>
<div class="col-md-5 mt32 mb64">
<div class="col-md-5 mt32">
<img class="img img-responsive" src="/website_sale/static/src/img/imac1.png"/>
</div>
</div>
</div>
</section>
<section data-snippet-id="text-image" class="mt16 mb16 dark">
<section data-snippet-id="text-image" class="mb16 dark">
<div class="container">
<div class="row">
<div class="text-center col-md-8 col-md-offset-2">
<h3>Beautiful widescreen display.</h3>
<div class="text-center col-md-8 col-md-offset-2 mt16">
<h2>Beautiful widescreen display.</h2>
<h3 class="text-muted">Brilliance onscreen. And behind it.</h3>
<p>
How did we make an already gorgeous widescreen display even better? By making it 75 percent less reflective. And by re-architecting the LCD and moving it right up against the cover glass. So you see your photos, games, movies, and everything else in vivid, lifelike detail.
</p>
</div>
<div class="col-md-10 col-md-offset-1 mt16 mb64">
<div class="col-md-10 col-md-offset-1">
<img class="img img-responsive" src="/website_sale/static/src/img/imac2.png" width="100%"/>
</div>
</div>
@ -385,9 +377,11 @@ Four-conductor 3.5-mm audio jack
<section data-snippet-id="image-text" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-5 col-md-offset-1 mt32">
<p>
Powered by fourth-generation Intel Core processors, this iMac is the fastest yet. Every model in the lineup comes standard with a quad-core Intel Core i5 processor, starting at 2.7GHz and topping out at 3.4GHz. And at the Apple Online Store, you can configure your iMac with an even more powerful Intel Core i7 processor, up to 3.5GHz.
<div class="col-md-5 col-md-offset-1 mt64">
<p class="text-muted">
Powered by fourth-generation Intel Core processors, this iMac is the fastest yet. Every model in the lineup comes standard with a quad-core Intel Core i5 processor, starting at 2.7GHz and topping out at 3.4GHz.
</p><p class="text-muted">
And at the Apple Online Store, you can configure your iMac with an even more powerful Intel Core i7 processor, up to 3.5GHz.
</p>
</div>
<div class="col-md-5 col-md-offset-1 mb32">
@ -419,14 +413,15 @@ Four-conductor 3.5-mm audio jack
<field name="description_sale">The sleek aluminium Apple Wireless Keyboard.
</field>
<field name="website_description" type="html">
<section data-snippet-id="image-text" class="mt16 mb16 dark">
<hr/>
<section data-snippet-id="image-text" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2 mt64 mb32">
<div class="col-md-10 col-md-offset-1 mt32 mb32">
<img class="img img-responsive" src="/website_sale/static/src/img/keyboard.png"/>
</div>
<div class="col-md-8 col-md-offset-2 mb32">
<h3>Apple Wireless Keyboard</h3>
<div class="col-md-8 col-md-offset-2 mb32 text-center">
<h3>A great Keyboard. Cordless.</h3>
<p>
The incredibly thin Apple Wireless Keyboard uses Bluetooth technology,
which makes it compatible with iPad. And youre free to type wherever
@ -441,18 +436,15 @@ Four-conductor 3.5-mm audio jack
<record id="product.product_product_11" model="product.product">
<field name="website_published" eval="True"/>
<field name="description_sale">Capacity: 16 GB
Height: 3.01 inches (76.5 mm)
Width: 1.56 inches (39.6 mm)
Depth: 0.21 inch (5.4 mm)
Weight: 1.1 ounces (31 grams)1
True volume: 0.95 cu. inch (15,537 cu. mm)
</field>
<field name="description_sale">Height: 3.01 inches
Width: 1.56 inches
Depth: 0.21 inch
Weight: 1.1 ounces</field>
<field name="website_description" type="html">
<section data-snippet-id="image-text" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-9 col-md-offset-1 mt16 mb64">
<div class="col-md-9 col-md-offset-1 mb16">
<img class="img img-responsive" src="/website_sale/static/src/img/overview_hero.png"/>
</div>
</div>
@ -464,12 +456,13 @@ True volume: 0.95 cu. inch (15,537 cu. mm)
<div class="col-md-5 col-md-offset-1 mt64">
<h3>Design. The thinnest iPod ever.</h3>
<p>
About the size of a credit card — and just 5.4 mm thin — iPod nano is the thinnest iPod ever made.
The 2.5-inch Multi-Touch display is nearly twice as big as the display on the previous iPod nano,
so you can see more of the music, photos, and videos you love.
Buttons let you quickly play, pause, change songs, or adjust the volume.
The smooth anodized aluminum design makes iPod nano feel as good as it sounds.
And iPod nano wouldnt be iPod nano without gorgeous, hard-to-choose-from color.
About the size of a credit card — and just 5.4 mm thin — iPod nano is the thinnest iPod ever made.
The 2.5-inch Multi-Touch display is nearly twice as big as the display on the previous iPod nano,
so you can see more of the music, photos, and videos you love.
</p><p>
Buttons let you quickly play, pause, change songs, or adjust the volume.
The smooth anodized aluminum design makes iPod nano feel as good as it sounds.
And iPod nano wouldnt be iPod nano without gorgeous, hard-to-choose-from color.
</p>
</div>
<div class="col-md-5">
@ -481,8 +474,8 @@ True volume: 0.95 cu. inch (15,537 cu. mm)
<section data-snippet-id="image-text" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-10 mt32 col-md-offset-1">
<h2>Music. It's what beats inside.</h2>
<div class="col-md-10 col-md-offset-1">
<h2 class="text-center">Music. It's what beats inside.</h2>
</div>
<div class="col-md-4 mt32 col-md-offset-1">
<img class="img img-responsive" src="/website_sale/static/src/img/purple.png"/>
@ -508,22 +501,20 @@ True volume: 0.95 cu. inch (15,537 cu. mm)
</div>
</div>
</section>
<section data-snippet-id="image-text" class="mt16 mb16 dark">
<section data-snippet-id="image-text" class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-10 mt32 col-md-offset-1">
<div class="col-md-12 text-center mb16">
<h2>Playlists. The perfect mix for every mood.</h2>
</div>
<div class="col-md-4 mt32 col-md-offset-1">
<img class="img img-responsive" src="/website_sale/static/src/img/plug_playlist.png"/>
</div>
<div class="col-md-6 mt32">
<div class="col-md-4">
<h3>Sync to your hearts content.</h3>
<p>
iTunes on your Mac or PC makes it easy to load up
your iPod. Just choose the playlists, audiobooks,
podcasts, and other audio files you want, then sync.
</p>
</div><div class="col-md-4">
<h3>When one playlist isnt enough.</h3>
<p>
You probably have multiple playlists in iTunes on your computer.
@ -532,6 +523,7 @@ True volume: 0.95 cu. inch (15,537 cu. mm)
mood strikes you. VoiceOver tells you the name of each playlist,
so its easy to switch between them and find the one you want without looking.
</p>
</div><div class="col-md-4">
<h3>Have Genius call the tunes.</h3>
<p>
Theres another way to get a good mix of music on iPod: Let Genius do the work.