diff --git a/website_seo/core/__init__.py b/website_seo/core/__init__.py index 89c2485..9d60a1a 100755 --- a/website_seo/core/__init__.py +++ b/website_seo/core/__init__.py @@ -18,4 +18,4 @@ # along with this program. If not, see . # ############################################################################## -import core +from . import core diff --git a/website_seo_blog/__init__.py b/website_seo_blog/__init__.py index f647841..efa4fe9 100755 --- a/website_seo_blog/__init__.py +++ b/website_seo_blog/__init__.py @@ -18,6 +18,6 @@ # along with this program. If not, see . # ############################################################################## -import controllers -import models -import tests +from . import controllers +from . import models +from . import tests diff --git a/website_seo_blog/tests/__init__.py b/website_seo_blog/tests/__init__.py index 5cd6799..7084442 100755 --- a/website_seo_blog/tests/__init__.py +++ b/website_seo_blog/tests/__init__.py @@ -18,5 +18,5 @@ # along with this program. If not, see . # ############################################################################## -import test_website_seo_blog -import test_website_seo_blog_controller +from . import test_website_seo_blog +from . import test_website_seo_blog_controller diff --git a/website_seo_blog/tests/test_website_seo_blog_controller.py b/website_seo_blog/tests/test_website_seo_blog_controller.py index a9edd9e..9e89afa 100755 --- a/website_seo_blog/tests/test_website_seo_blog_controller.py +++ b/website_seo_blog/tests/test_website_seo_blog_controller.py @@ -1,6 +1,26 @@ # -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## import openerp + ODOO_NOT_FOUND_MESSAGE = '404: Page not found!' diff --git a/website_seo_sale/README.rst b/website_seo_sale/README.rst new file mode 100755 index 0000000..e69de29 diff --git a/website_seo_sale/__init__.py b/website_seo_sale/__init__.py new file mode 100644 index 0000000..c8f6e41 --- /dev/null +++ b/website_seo_sale/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import controllers +from . import models diff --git a/website_seo_sale/__openerp__.py b/website_seo_sale/__openerp__.py new file mode 100644 index 0000000..ef1df35 --- /dev/null +++ b/website_seo_sale/__openerp__.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +{ + 'name': 'eCommerce SEO Optimized', + 'category': 'Website', + 'sequence': 55, + 'summary': 'Sell Your Products Online', + 'website': 'http://www.bloopark.de', + 'version': '1.0', + 'author': "bloopark systems GmbH & Co. KG ," + "Odoo Community Association (OCA)", + 'description': """ +OpenERP E-Commerce SEO Optimized +================== + + """, + 'depends': ['website_seo', 'website_sale'], + 'data': [ + 'views/templates.xml', + ], + 'installable': True, + 'application': False, +} diff --git a/website_seo_sale/controllers/__init__.py b/website_seo_sale/controllers/__init__.py new file mode 100644 index 0000000..0b4a657 --- /dev/null +++ b/website_seo_sale/controllers/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import main diff --git a/website_seo_sale/controllers/main.py b/website_seo_sale/controllers/main.py new file mode 100644 index 0000000..9cd58c4 --- /dev/null +++ b/website_seo_sale/controllers/main.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import werkzeug + +from openerp import http +from openerp.http import request +from openerp.addons.website_sale.controllers.main import website_sale, PPG, PPR, QueryURL, table_compute +from openerp.addons.website_seo_sale.models.product import slug +from openerp.tools.translate import _ + + +class WebsiteSeoSale(website_sale): + + @http.route([ + '/shop', + '/category/', + '/shop/category/', + ], type='http', auth="public", website=True) + def shop(self, page=0, category=None, search='', ppg=False, **post): + + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + domain = request.website.sale_product_domain() + + if ppg: + try: + ppg = int(ppg) + except ValueError: + ppg = PPG + post["ppg"] = ppg + else: + ppg = PPG + + if search: + for srch in search.split(" "): + domain += ['|', '|', '|', ('name', 'ilike', srch), ('description', 'ilike', srch), + ('description_sale', 'ilike', srch), ('product_variant_ids.default_code', 'ilike', srch)] + if category: + domain += [('public_categ_ids', 'child_of', int(category))] + attrib_list = request.httprequest.args.getlist('attrib') + attrib_values = [map(int,v.split("-")) for v in attrib_list if v] + attrib_set = set([v[1] for v in attrib_values]) + + if attrib_values: + attrib = None + ids = [] + for value in attrib_values: + if not attrib: + attrib = value[0] + ids.append(value[1]) + elif value[0] == attrib: + ids.append(value[1]) + else: + domain += [('attribute_line_ids.value_ids', 'in', ids)] + attrib = value[0] + ids = [value[1]] + if attrib: + domain += [('attribute_line_ids.value_ids', 'in', ids)] + + keep = QueryURL('/shop', category=category and int(category), search=search, attrib=attrib_list) + + if not context.get('pricelist'): + pricelist = self.get_pricelist() + context['pricelist'] = int(pricelist) + else: + pricelist = pool.get('product.pricelist').browse(cr, uid, context['pricelist'], context) + url = "/" + if search: + post["search"] = search + if category: + category = pool['product.public.category'].browse(cr, uid, int(category), context=context) + url = "/%s" % slug(category) + if attrib_list: + post['attrib'] = attrib_list + + style_obj = pool['product.style'] + style_ids = style_obj.search(cr, uid, [], context=context) + styles = style_obj.browse(cr, uid, style_ids, context=context) + + category_obj = pool['product.public.category'] + category_ids = category_obj.search(cr, uid, [('parent_id', '=', False)], context=context) + categs = category_obj.browse(cr, uid, category_ids, context=context) + + product_obj = pool.get('product.template') + + parent_category_ids = [] + if category: + parent_category_ids = [category.id] + current_category = category + while current_category.parent_id: + parent_category_ids.append(current_category.parent_id.id) + current_category = current_category.parent_id + + product_count = product_obj.search_count(cr, uid, domain, context=context) + pager = request.website.pager(url=url, total=product_count, page=page, step=ppg, scope=7, url_args=post) + product_ids = product_obj.search(cr, uid, domain, limit=ppg, offset=pager['offset'], order='website_published desc, website_sequence asc', context=context) + products = product_obj.browse(cr, uid, product_ids, context=context) + + attributes_obj = request.registry['product.attribute'] + attributes_ids = attributes_obj.search(cr, uid, [('attribute_line_ids.product_tmpl_id', 'in', product_ids)], context=context) + attributes = attributes_obj.browse(cr, uid, attributes_ids, context=context) + + from_currency = pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id + to_currency = pricelist.currency_id + compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context) + + values = { + 'search': search, + 'category': category, + 'attrib_values': attrib_values, + 'attrib_set': attrib_set, + 'pager': pager, + 'pricelist': pricelist, + 'products': products, + 'bins': table_compute().process(products, ppg), + 'rows': PPR, + 'styles': styles, + 'categories': categs, + 'attributes': attributes, + 'compute_currency': compute_currency, + 'keep': keep, + 'parent_category_ids': parent_category_ids, + 'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids], + 'attrib_encode': lambda attribs: werkzeug.url_encode([('attrib',i) for i in attribs]), + } + if category: + values['main_object'] = category + return request.website.render("website_sale.products", values) + + @http.route(['/'], type='http', auth="public", website=True) + def product(self, product, category='', search='', **kwargs): + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + category_obj = pool['product.public.category'] + template_obj = pool['product.template'] + + context.update(active_id=product.id) + + if category: + category = category_obj.browse(cr, uid, int(category), context=context) + category = category if category.exists() else False + + attrib_list = request.httprequest.args.getlist('attrib') + attrib_values = [map(int,v.split("-")) for v in attrib_list if v] + attrib_set = set([v[1] for v in attrib_values]) + + keep = QueryURL('/shop', category=category and category.id, search=search, attrib=attrib_list) + + category_ids = category_obj.search(cr, uid, [('parent_id', '=', False)], context=context) + categs = category_obj.browse(cr, uid, category_ids, context=context) + + pricelist = self.get_pricelist() + + from_currency = pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id + to_currency = pricelist.currency_id + compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context) + + # get the rating attached to a mail.message, and the rating stats of the product + Rating = pool['rating.rating'] + rating_ids = Rating.search(cr, uid, [('message_id', 'in', product.website_message_ids.ids)], context=context) + ratings = Rating.browse(cr, uid, rating_ids, context=context) + rating_message_values = dict([(record.message_id.id, record.rating) for record in ratings]) + rating_product = product.rating_get_stats([('website_published', '=', True)]) + + if not context.get('pricelist'): + context['pricelist'] = int(self.get_pricelist()) + product = template_obj.browse(cr, uid, int(product), context=context) + + values = { + 'search': search, + 'category': category, + 'pricelist': pricelist, + 'attrib_values': attrib_values, + 'compute_currency': compute_currency, + 'attrib_set': attrib_set, + 'keep': keep, + 'categories': categs, + 'main_object': product, + 'product': product, + 'get_attribute_value_ids': self.get_attribute_value_ids, + 'rating_message_values': rating_message_values, + 'rating_product': rating_product + } + return request.website.render("website_sale.product", values) + + @http.route(['/shop/add_product'], type='http', auth="user", methods=['POST'], website=True) + def add_product(self, name=None, category=0, **post): + cr, uid, context, pool = request.cr, request.uid, request.context, request.registry + if not name: + name = _("New Product") + product_obj = request.registry.get('product.product') + product_id = product_obj.create(cr, uid, { 'name': name, 'public_categ_ids': category }, context=context) + product = product_obj.browse(cr, uid, product_id, context=context) + + return request.redirect("/%s?enable_editor=1" % slug(product.product_tmpl_id)) \ No newline at end of file diff --git a/website_seo_sale/models/__init__.py b/website_seo_sale/models/__init__.py new file mode 100644 index 0000000..244dab4 --- /dev/null +++ b/website_seo_sale/models/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import ir_http +from . import ir_ui_view +from . import product diff --git a/website_seo_sale/models/ir_http.py b/website_seo_sale/models/ir_http.py new file mode 100644 index 0000000..e29bac8 --- /dev/null +++ b/website_seo_sale/models/ir_http.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import orm +from openerp.addons.website.models.ir_http import ModelConverter, RequestUID +from openerp.addons.website_seo_sale.models.product import slug, _UNSLUG_RE +from openerp.http import request +import re + + +class IrHttp(orm.TransientModel): + _inherit = 'ir.http' + + def _get_converters(self): + res = super(IrHttp, self)._get_converters() + res['model'] = UnderscoredModelConverter + return res + +class UnderscoredModelConverter(ModelConverter): + + def __init__(self, url_map, model=False, domain='[]'): + super(ModelConverter, self).__init__(url_map, model) + self.domain = domain + self.regex = _UNSLUG_RE.pattern + + def to_url(self, value): + return slug(value) + + def to_python(self, value): + m = re.match(self.regex, value) + _uid = RequestUID(value=value, match=m, converter=self) + + res = request.registry[self.model].search(request.cr, 1, [('name', '=', value.replace('_', ' '))]) + if res: + return request.registry[self.model].browse(request.cr, _uid, res, context=request.context) \ No newline at end of file diff --git a/website_seo_sale/models/ir_ui_view.py b/website_seo_sale/models/ir_ui_view.py new file mode 100755 index 0000000..e25ea13 --- /dev/null +++ b/website_seo_sale/models/ir_ui_view.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp import api, models +from openerp.addons.website_seo_sale.models.product import slug + + +class View(models.Model): + + """Update view model with additional SEO variables.""" + + _name = 'ir.ui.view' + _inherit = ['ir.ui.view', 'website.seo.metadata'] + + + @api.cr_uid_ids_context + def render(self, cr, uid, id_or_xml_id, values=None, engine='ir.qweb', context=None): + """Add additional helper variables. + + Add slug function with additional seo url handling and the query string + of the http request environment to the values object. + """ + if values is None: + values = {} + + values.update({ + 'slaag': slug, + }) + + return super(View, self).render(cr, uid, id_or_xml_id, values=values, + engine=engine, context=context) diff --git a/website_seo_sale/models/product.py b/website_seo_sale/models/product.py new file mode 100644 index 0000000..5456456 --- /dev/null +++ b/website_seo_sale/models/product.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import osv +from openerp.osv import orm +import re +from openerp.tools.translate import _ +from openerp.exceptions import ValidationError + + +_UNSLUG_RE = re.compile(r'(?:(\w{1,2}|\w[A-Za-z0-9-_]+?\w))(?=$|/)') + +def slug(value): + if isinstance(value, orm.browse_record): + name = value.display_name + else: + name = value + + return name.replace(' ', '_') + + +class ProductTemplate(osv.Model): + _inherit = ["product.template", "website.seo.metadata", 'website.published.mixin', 'rating.mixin'] + _order = 'website_published desc, website_sequence desc, name' + _name = 'product.template' + _mail_post_access = 'read' + + def _website_url(self, cr, uid, ids, field_name, arg, context=None): + res = super(product_template, self)._website_url(cr, uid, ids, field_name, arg, context=context) + for product in self.browse(cr, uid, ids, context=context): + res[product.id] = "/%s" % slug(product.name) + return res + + +class ProductProduct(osv.Model): + _inherit = "product.product" + + def open_website_url(self, cr, uid, ids, context=None): + template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id + return self.pool['product.template'].open_website_url(cr, uid, [template_id], context=context) + + def website_publish_button(self, cr, uid, ids, context=None): + template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id + return self.pool['product.template'].website_publish_button(cr, uid, [template_id], context=context) + + def website_publish_button(self, cr, uid, ids, context=None): + template_id = self.browse(cr, uid, ids, context=context).product_tmpl_id.id + return self.pool['product.template'].website_publish_button(cr, uid, [template_id], context=context) + + def validate_seo_url(self, seo_url): + """Validate a manual entered SEO url.""" + if not seo_url or not bool(re.match('^([.a-zA-Z0-9-_]+)$', seo_url)): + raise ValidationError(_('Only a-z, A-Z, 0-9, - and _ are allowed ' + 'characters for the SEO url.')) + return True + + +class ProductPublicCategory(osv.osv): + _inherit = ["product.public.category"] + + def name_get(self, cr, uid, ids, context=None): + res = [] + for cat in self.browse(cr, uid, ids, context=context): + res.append((cat.id, cat.name)) + return res \ No newline at end of file diff --git a/website_seo_sale/tests/__init__.py b/website_seo_sale/tests/__init__.py new file mode 100644 index 0000000..382d76a --- /dev/null +++ b/website_seo_sale/tests/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from . import test_website_seo_sale diff --git a/website_seo_sale/tests/test_website_seo_sale.py b/website_seo_sale/tests/test_website_seo_sale.py new file mode 100644 index 0000000..87c2f35 --- /dev/null +++ b/website_seo_sale/tests/test_website_seo_sale.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, an open source suite of business apps +# This module copyright (C) 2015 bloopark systems (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import openerp.tests +from openerp.addons.website_seo_sale.models.product import slug + + +@openerp.tests.common.at_install(False) +@openerp.tests.common.post_install(True) +class TestWebsiteSeoSale(openerp.tests.common.TransactionCase): + def test_slug_product(self): + self.assertEqual(slug('Test Product'), 'Test_Product') + + def test_product_url(self): + self.assertTrue(self.env['product.product'].validate_seo_url('Sample_Product')) diff --git a/website_seo_sale/views/templates.xml b/website_seo_sale/views/templates.xml new file mode 100644 index 0000000..dfc8073 --- /dev/null +++ b/website_seo_sale/views/templates.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file