diff options
Diffstat (limited to 'addons/website_project')
| -rw-r--r-- | addons/website_project/__init__.py | 5 | ||||
| -rw-r--r-- | addons/website_project/__manifest__.py | 18 | ||||
| -rw-r--r-- | addons/website_project/controllers/__init__.py | 4 | ||||
| -rw-r--r-- | addons/website_project/controllers/rating.py | 98 | ||||
| -rw-r--r-- | addons/website_project/models/__init__.py | 4 | ||||
| -rw-r--r-- | addons/website_project/models/project.py | 23 | ||||
| -rw-r--r-- | addons/website_project/views/project_project_views.xml | 16 | ||||
| -rw-r--r-- | addons/website_project/views/project_rating_templates.xml | 212 |
8 files changed, 380 insertions, 0 deletions
diff --git a/addons/website_project/__init__.py b/addons/website_project/__init__.py new file mode 100644 index 00000000000..7d34c7c054a --- /dev/null +++ b/addons/website_project/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import controllers +from . import models diff --git a/addons/website_project/__manifest__.py b/addons/website_project/__manifest__.py new file mode 100644 index 00000000000..cb467db65e6 --- /dev/null +++ b/addons/website_project/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Website Project', + 'category': 'Website/Website', + 'summary': 'Publish your project ratings', + 'version': '1.0', + 'description': """ +Allows to publish all ratings for a particular project.""", + 'depends': ['website', 'project'], + 'data': [ + 'views/project_rating_templates.xml', + 'views/project_project_views.xml', + ], + 'installable': True, + 'auto_install': True, +} diff --git a/addons/website_project/controllers/__init__.py b/addons/website_project/controllers/__init__.py new file mode 100644 index 00000000000..7367c6c8192 --- /dev/null +++ b/addons/website_project/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import rating diff --git a/addons/website_project/controllers/rating.py b/addons/website_project/controllers/rating.py new file mode 100644 index 00000000000..7e0c3d5dda2 --- /dev/null +++ b/addons/website_project/controllers/rating.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from werkzeug.exceptions import NotFound + +from odoo import http +from odoo.http import request + + +class RatingProject(http.Controller): + + @http.route(['/project/rating'], type='http', auth="public", website=True, sitemap=True) + def index(self, **kw): + projects = request.env['project.project'].sudo().search([ + ('rating_active', '=', True), + ('is_published', '=', True)]) + values = {'projects': projects} + return request.render('website_project.rating_index', values) + + def _calculate_period_partner_stats(self, project_id): + # get raw data: number of rating by rated partner, by rating value, by period + request.env.cr.execute(""" + SELECT + rated_partner_id, + rating, + COUNT(rating) as rating_count, + CASE + WHEN now()::date - write_date::date BETWEEN 0 AND 6 Then 'days_06' + WHEN now()::date - write_date::date BETWEEN 0 AND 15 Then 'days_15' + WHEN now()::date - write_date::date BETWEEN 0 AND 30 Then 'days_30' + WHEN now()::date - write_date::date BETWEEN 0 AND 90 Then 'days_90' + END AS period + FROM + rating_rating + WHERE + parent_res_model = 'project.project' + AND parent_res_id = %s + AND res_model = 'project.task' + AND rated_partner_id IS NOT NULL + AND write_date >= current_date - interval '90' day + AND rating IN (1, 3, 5) + GROUP BY + rated_partner_id, rating, period + """, (project_id, )) + + raw_data = request.env.cr.dictfetchall() + + # periodical statistics + default_period_dict = {'rating_5': 0, 'rating_3': 0, 'rating_1': 0, 'total': 0} + period_statistics = { + 'days_06': dict(default_period_dict), + 'days_15': dict(default_period_dict), + 'days_30': dict(default_period_dict), + 'days_90': dict(default_period_dict), + } + for period_statistics_key in period_statistics.keys(): + for row in raw_data: + if row['period'] <= period_statistics_key: + period_statistics[period_statistics_key]['rating_%s' % (int(row['rating']),)] += row['rating_count'] + period_statistics[period_statistics_key]['total'] += row['rating_count'] + + # partner statistics + default_partner_dict = {'rating_5': 0, 'rating_3': 0, 'rating_1': 0, 'total': 0, 'rated_partner': None, 'percentage_happy': 0.0} + partner_statistics = {} + for row in raw_data: + if row['period'] <= 'days_15': + if row['rated_partner_id'] not in partner_statistics: + partner_statistics[row['rated_partner_id']] = dict(default_partner_dict) + partner_statistics[row['rated_partner_id']]['rated_partner'] = request.env['res.partner'].sudo().browse(row['rated_partner_id']) + partner_statistics[row['rated_partner_id']]['rating_%s' % (int(row['rating']),)] += row['rating_count'] + partner_statistics[row['rated_partner_id']]['total'] += row['rating_count'] + + for partner_id, stat_values in partner_statistics.items(): + stat_values['percentage_happy'] = (stat_values['rating_5'] / float(stat_values['total'])) * 100 if stat_values['total'] else 0.0 + + return { + 'partner_statistics': partner_statistics, + 'period_statistics': period_statistics, + } + + @http.route(['/project/rating/<int:project_id>'], type='http', auth="public", website=True) + def page(self, project_id=None, **kw): + user = request.env.user + project = request.env['project.project'].sudo().browse(project_id) + # to avoid giving any access rights on projects to the public user, let's use sudo + # and check if the user should be able to view the project (project managers only if it's unpublished or has no rating) + if not (project.rating_active and project.is_published) and not user.with_user(user).has_group('project.group_project_manager'): + raise NotFound() + + return request.render('website_project.rating_project_rating_page', { + 'project': project, + 'ratings': request.env['rating.rating'].sudo().search([ + ('consumed', '=', True), + ('parent_res_model', '=', 'project.project'), + ('parent_res_id', '=', project_id) + ], order='write_date DESC', limit=50), + 'statistics': self._calculate_period_partner_stats(project_id), + }) diff --git a/addons/website_project/models/__init__.py b/addons/website_project/models/__init__.py new file mode 100644 index 00000000000..527e2954c06 --- /dev/null +++ b/addons/website_project/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import project diff --git a/addons/website_project/models/project.py b/addons/website_project/models/project.py new file mode 100644 index 00000000000..ded92b699a2 --- /dev/null +++ b/addons/website_project/models/project.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class Project(models.Model): + _name = 'project.project' + _inherit = ['project.project', 'website.published.mixin'] + + def action_view_all_rating(self): + if self.website_published: + return self.open_website_url() + return super(Project, self).action_view_all_rating() + + def open_website_url(self): + """ return the action to see all the rating of the project, and activate default filters """ + return { + 'type': 'ir.actions.act_url', + 'name': "Redirect to the Website Project Rating Page", + 'target': 'self', + 'url': "/project/rating/%s" % (self.id) + } diff --git a/addons/website_project/views/project_project_views.xml b/addons/website_project/views/project_project_views.xml new file mode 100644 index 00000000000..354adc30d5b --- /dev/null +++ b/addons/website_project/views/project_project_views.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="project_project_view_form" model="ir.ui.view"> + <field name="name">project.project.form</field> + <field name="model">project.project</field> + <field name="inherit_id" ref="project.edit_project"/> + <field name="arch" type="xml"> + <xpath expr="//div[@name='button_box']" position="inside"> + <field name="is_published" + widget="website_redirect_button" + groups="project.group_project_rating" + attrs="{'invisible': [('rating_active', '=', False)]}"/> + </xpath> + </field> + </record> +</odoo> diff --git a/addons/website_project/views/project_rating_templates.xml b/addons/website_project/views/project_rating_templates.xml new file mode 100644 index 00000000000..197bf781b30 --- /dev/null +++ b/addons/website_project/views/project_rating_templates.xml @@ -0,0 +1,212 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <data> + + <!-- Page : List of project --> + <template id="rating_index" name="Project Rating List"> + <t t-call="portal.frontend_layout"> + <t t-set="additional_title">Project Satisfaction</t> + <div id="wrap" class="row mt4"> + <div class="oe_structure" id="oe_structure_project_rating_index_1"/> + <div class="container o_portal_project_rating"> + <h3>Projects</h3> + <div class="oe_structure" id="oe_structure_project_rating_index_2"/> + <div class="row"> + <t t-if="not projects"> + <div class="mt64 text-center"> + No project rating published for now. + </div> + </t> + <t t-foreach="projects" t-as="project"> + <div t-if="project.rating_percentage_satisfaction != -1" class="col-md-6 col-4 col-lg-4 col-xl-4"> + <div class="card"> + <div class="card-body"> + <div class="caption"> + <span class="badge badge-secondary float-right"><t t-esc="project.privacy_visibility"/></span> + <h4><t t-esc="project.name"/></h4> + <p t-if="project.date" class="text-muted"> + <i class="fa fa-calendar"/> End date : <t t-esc="project.date"/> + </p> + <p t-if="project.alias_name and project.alias_domain" class="text-muted"> + <i class="fa fa-envelope"/> Email : <t t-esc="project.alias_name"/>@<t t-esc="project.alias_domain"/> + </p> + <div class="row"> + <div class="col-lg-6 text-center"> + <h2><t t-esc="len(project.task_ids)"/></h2> + <p><t t-esc="project.label_tasks"/></p> + </div> + </div> + </div> + <div class="caption"> + <p> + <a role="button" t-att-href="'/project/rating/%s' % project.id" class="btn btn-primary btn-lg btn-block"> + <i class="fa fa-arrow-circle-right "/> See the feedbacks + </a> + </p> + </div> + </div> + </div> + </div> + </t> + </div> + </div> + <div class="oe_structure" id="oe_structure_project_rating_index_3"/> + </div> + </t> + </template> + + <!-- Page : Project Rating Page (and widgets) --> + <template id="portal_project_rating_progressbar"> + <div class="progress o_rating_progress"> + <t t-set="percent_rating_5" t-value="int(round((stats['rating_5'] / float(stats['total'])) * 100)) if stats['total'] else 0.0"/> + <div class="progress-bar bg-success" t-attf-style="width: #{percent_rating_5}%;" title="Happy"> + <t t-esc="percent_rating_5"/>% + </div> + <t t-set="percent_rating_3" t-value="int(round((stats['rating_3'] / float(stats['total'])) * 100)) if stats['total'] else 0.0"/> + <div class="progress-bar bg-warning" t-attf-style="width: #{percent_rating_3}%;" title="Average"> + <t t-esc="percent_rating_3"/>% + </div> + <t t-set="percent_rating_1" t-value="int(round((stats['rating_1'] / float(stats['total'])) * 100)) if stats['total'] else 0.0"/> + <div class="progress-bar bg-danger" t-attf-style="width: #{percent_rating_1}%;" title="Bad"> + <t t-esc="percent_rating_1"/>% + </div> + </div> + </template> + + <template id="portal_project_rating_partner_stat"> + <t t-foreach="partner_stats.values()" t-as="partner_stat"> + <div class="row mt8" t-if="partner_stat['total']" title="Top 5 partner ratings of last 15 days."> + <div class="col-md-1"> + <img t-if="partner_stat['rated_partner'].image_1024" class="o_top_partner_image" t-att-src="image_data_uri(partner_stat['rated_partner'].image_1024)" alt="Rated partner"/> + <img t-if="not partner_stat['rated_partner'].image_1024" class="o_top_partner_image" src='/web/static/src/img/placeholder.png' alt="Placeholder"/> + </div> + <div class="col-md-4 o_smiley_no_padding_right"> + <span class="ml8" t-esc="partner_stat['rated_partner'].name"/> + </div> + <div class="col-md-4 o_smiley_no_padding_right"> + <div class="col-md-4 o_smiley_no_padding_right o_smiley_no_padding_left"> + <span t-if="partner_stat['rating_5']"> + <img title="happy" alt="Happy face" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_5.png'/> + <span class="o_rating_count" t-esc="partner_stat['rating_5']"/> + </span> + <span t-if="not partner_stat['rating_5']"> + <img title="happy" alt="Happy face" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_5.png'/> + <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_5']"/> + </span> + </div> + <div class="col-md-4 o_smiley_no_padding_right o_smiley_no_padding_left"> + <span t-if="partner_stat['rating_3']"> + <img title="average" alt="Neutral face" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_3.png'/> + <span class="o_rating_count" t-esc="partner_stat['rating_3']"/> + </span> + <span t-if="not partner_stat['rating_3']"> + <img title="average" alt="Neutral face" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_3.png'/> + <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_3']"/> + </span> + </div> + <div class="col-md-4 o_smiley_no_padding_right o_smiley_no_padding_left"> + <span t-if="partner_stat['rating_1']"> + <img title="bad" alt="Sad face" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_1.png'/> + <span class="o_rating_count" t-esc="partner_stat['rating_1']"/> + </span> + <span t-if="not partner_stat['rating_1']"> + <img title="bad" alt="Sad face" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_1.png'/> + <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_1']"/> + </span> + </div> + </div> + <div class="col-md-3 o_smiley_no_padding_left"> + <strong class="float-right"><t t-esc="int(round(partner_stat['percentage_happy']))"/>%</strong> + </div> + </div> + </t> + </template> + + <template id="portal_project_rating_popover"> + <div t-attf-id="rating_#{rating.id}" class="container d-none"> + <div class="row"> + <div class="col-lg-4"> + <img t-if="rating.partner_id.image_1024" class="img-fluid" t-att-src="image_data_uri(rating.partner_id.image_1024)" alt="Rated partner"/> + <img t-if="not rating.partner_id.image_1024" class="img-fluid" src='/web/static/src/img/placeholder.png' alt="Placeholder"/> + </div> + <div class="col-lg-8"> + <div class="mt4"> + <strong><t t-esc="rating.rated_partner_id.name"/></strong> - <span class="rating_timeduration"><t t-esc="rating.create_date"/></span> + </div> + <strong>Rated by: </strong><t t-esc="rating.partner_id.name" /> + </div> + </div> + <div class="row o_top_partner_feedback" t-if="rating.feedback"> + <div class="col-lg-12"> + <t t-esc="rating.feedback" /> + </div> + </div> + </div> + </template> + + <template id="rating_project_rating_page" name="Project Rating Page"> + <t t-call="portal.frontend_layout"> + <div id="wrap" class="row mt4"> + <div class="oe_structure" id="oe_structure_project_rating_page_1"/> + <div class="container o_project o_portal_project_rating"> + <br/> + <h2 t-esc="project.name"/> + <br/> + <div class="row mb32"> + <div class="col-lg-12"> + <div class="o_page_header"> + <div class="justify-content-end" groups="project.group_project_manager"> + <t t-call="website.publish_management"> + <t t-set="object" t-value="project"/> + <t t-set="publish_edit" t-value="True"/> + </t> + </div> + <h3>Customer Ratings</h3> + </div> + <div class="row"> + <div class="col-lg-7"> + <t t-foreach="ratings" t-as="rating"> + <img t-attf-src='/rating/static/src/img/#{rating._get_rating_image_filename()}' + class="mt4 o_rating_image" + t-att-alt="rating.res_name" + t-att-data-id="rating.id" + t-att-data-rating-date="rating.write_date"/> + <t t-call="website_project.portal_project_rating_popover"/> + </t> + </div> + <div class="col-lg-5 o_vertical_separator"> + <div t-if="statistics['period_statistics']['days_06']"> + <h5>Last 7 days</h5> + <t t-call="website_project.portal_project_rating_progressbar"> + <t t-set="stats" t-value="statistics['period_statistics']['days_06']"/> + </t> + </div> + <div t-if="statistics['period_statistics']['days_30']"> + <h5>Last 30 days</h5> + <t t-call="website_project.portal_project_rating_progressbar"> + <t t-set="stats" t-value="statistics['period_statistics']['days_30']"/> + </t> + </div> + <div t-if="statistics['period_statistics']['days_90']"> + <h5>Last 3 months</h5> + <t t-call="website_project.portal_project_rating_progressbar"> + <t t-set="stats" t-value="statistics['period_statistics']['days_90']"/> + </t> + </div> + <div> + <t t-call="website_project.portal_project_rating_partner_stat"> + <t t-set="partner_stats" t-value="statistics['partner_statistics']"/> + </t> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="oe_structure" id="oe_structure_project_rating_page_2"/> + </div> + </t> + </template> + + </data> +</odoo> |
