[MERGE] survey, website_slides: fix various issues

PURPOSE

Fix various issues in eLearning application after new round of testing
and bug hunt. Some surveys fixes are included as they are used a lot
in eLearning.

SPECIFICATIONS

[FIX] mail: remove channel and partner default context keys when creating
  followers
[FIX] survey: fix another crashing computed field
[FIX] survey: set email_from on certification mail template
[FIX] website_profile: fix another broken image thingy
[FIX] website_slides: make content stat button redirection match counters
[FIX] website_slides: remove unnecessary action
[FIX] website_{slides, profile}: fix display of not square user image
[FIX] website_slides: avoid admin being twice member of courses in demo data
[FIX] website_slides: still another fix of computed fields
[FIX] website_sale_slides: synchronize publish status of a channel and its
  product
[FIX] website_slides_survey: create certificates with scoring by default

See sub commits for more details.

LINKS

Task 2065425 (avatar ratio badly displayed)
Task 2067324 (counters / action on content mismatch)
Task 2067872 (eLearning internal testing)
Task 2058598 (survey internal testing)

closes odoo/odoo#36756

Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2019-09-13 13:10:57 +02:00
committed by GitHub
17 changed files with 73 additions and 40 deletions
+1 -1
View File
@@ -241,7 +241,7 @@ GROUP BY fol.id%s""" % (
:param check_existing: see ``_add_followers``;
:param existing_policy: see ``_add_followers``;
"""
sudo_self = self.sudo()
sudo_self = self.sudo().with_context(default_partner_id=False, default_channel_id=False)
if not partner_subtypes and not channel_subtypes: # no subtypes -> default computation, no force, skip existing
new, upd = self._add_default_followers(res_model, res_ids, partner_ids, channel_ids, customer_ids=customer_ids)
else:
+3 -2
View File
@@ -2,7 +2,7 @@
<odoo>
<data noupdate="1">
<record id="mail_template_user_input_invite" model="mail.template">
<field name="name">Survey: Send by email</field>
<field name="name">Survey: Invite</field>
<field name="model_id" ref="model_survey_user_input" />
<field name="subject">Participate to ${object.survey_id.title} survey</field>
<field name="email_to">${(object.partner_id.email_formatted or object.email) |safe}</field>
@@ -39,9 +39,10 @@
<!-- Certification Email template -->
<record id="mail_template_certification" model="mail.template">
<field name="name">Certification: Send by email</field>
<field name="name">Survey: Send certification by email</field>
<field name="model_id" ref="survey.model_survey_user_input"/>
<field name="subject">Certification: ${object.survey_id.display_name}</field>
<field name="email_from">${(object.survey_id.create_uid.email_formatted or user.email_formatted or user.company_id.catchall) |safe}</field>
<field name="email_to">${(object.partner_id.email_formatted or object.email) |safe}</field>
<field name="body_html" type="xml">
<div style="background:#F0F0F0;color:#515166;padding:10px 0px;font-family:Arial,Helvetica,sans-serif;font-size:14px;">
+11 -5
View File
@@ -123,7 +123,11 @@ class Survey(models.Model):
@api.depends('user_input_ids.state', 'user_input_ids.test_entry', 'user_input_ids.quizz_score', 'user_input_ids.quizz_passed')
def _compute_survey_statistic(self):
stat = dict((cid, dict(answer_count=0, answer_done_count=0, success_count=0, answer_score_avg_total=0.0)) for cid in self.ids)
default_vals = {
'answer_count': 0, 'answer_done_count': 0, 'success_count': 0,
'answer_score_avg': 0.0, 'success_ratio': 0.0
}
stat = dict((cid, dict(default_vals, answer_score_avg_total=0.0)) for cid in self.ids)
UserInput = self.env['survey.user_input']
base_domain = ['&', ('survey_id', 'in', self.ids), ('test_entry', '!=', True)]
@@ -136,11 +140,13 @@ class Survey(models.Model):
if item['quizz_passed']:
stat[item['survey_id'][0]]['success_count'] += item['__count']
for survey_id, values in stat.items():
avg_total = stat[survey_id].pop('answer_score_avg_total')
stat[survey_id]['answer_score_avg'] = avg_total / (stat[survey_id]['answer_done_count'] or 1)
stat[survey_id]['success_ratio'] = (stat[survey_id]['success_count'] / (stat[survey_id]['answer_done_count'] or 1.0))*100
for survey in self:
avg_total = stat[survey.id].pop('answer_score_avg_total')
stat[survey.id]['answer_score_avg'] = avg_total / (stat[survey.id]['answer_done_count'] or 1)
stat[survey.id]['success_ratio'] = (stat[survey.id]['success_count'] / (stat[survey.id]['answer_done_count'] or 1.0))*100
survey.update(stat[survey.id])
survey.update(stat.get(survey._origin.id, default_vals))
def _compute_survey_url(self):
""" Computes a public URL for the survey """
@@ -14,7 +14,7 @@ class TestUi(tests.HttpCase):
user_demo.write({
'groups_id': [(5, 0), (4, self.env.ref('base.group_user').id)]
})
self.env.ref('website_slides.slide_channel_demo_6_furn3')._remove_membership(self.env.ref('base.partner_demo'))
self.env.ref('website_slides.slide_channel_demo_6_furn3')._remove_membership(self.env.ref('base.partner_demo').ids)
self.phantom_js(
'/slides',
+2 -2
View File
@@ -137,10 +137,10 @@ class WebsiteProfile(http.Controller):
}
if 'clear_image' in kwargs:
values['image'] = False
values['image_1920'] = False
elif kwargs.get('ufile'):
image = kwargs.get('ufile').read()
values['image'] = base64.b64encode(image)
values['image_1920'] = base64.b64encode(image)
if request.uid == user.id: # the controller allows to edit only its own privacy settings; use partner management for other cases
values['website_published'] = kwargs.get('website_published') == 'True'
@@ -514,7 +514,9 @@
<div class="card text-center mb-2 border-bottom-0 o_wprofile_pointer">
<div class="card-body">
<div class="d-inline-block position-relative">
<img class="rounded-circle img-fluid" t-att-src="'/profile/avatar/%s?field=image_128%s' % (user['id'], '&amp;res_model=%s&amp;res_id=%s' % (record._name, record.id) if record else '')"/>
<img class="rounded-circle img-fluid"
style="width: 128px; height: 128px; object-fit: cover;"
t-att-src="'/profile/avatar/%s?field=image_256%s' % (user['id'], '&amp;res_model=%s&amp;res_id=%s' % (record._name, record.id) if record else '')"/>
<img class="position-absolute" t-attf-src="/website_profile/static/src/img/rank_#{user_index + 1}.svg" alt="User rank" style="bottom: 0; right: -10px"/>
</div>
<h3 class="mt-2 mb-0" t-esc="user['name']"></h3>
@@ -31,6 +31,23 @@ class Channel(models.Model):
for channel in self:
channel.product_sale_revenues = rg_data.get(channel.product_id.id, 0)
@api.model
def create(self, vals):
channel = super(Channel, self).create(vals)
if channel.enroll == 'payment':
channel._synchronize_product_publish()
return channel
def write(self, vals):
res = super(Channel, self).write(vals)
if 'is_published' in vals:
self.filtered(lambda channel: channel.enroll == 'payment')._synchronize_product_publish()
return res
def _synchronize_product_publish(self):
self.filtered(lambda channel: channel.is_published and not channel.product_id.is_published).sudo().product_id.write({'is_published': True})
self.filtered(lambda channel: not channel.is_published and channel.product_id.is_published).sudo().product_id.write({'is_published': False})
def action_view_sales(self):
action = self.env.ref('website_sale_slides.sale_report_action_slides').read()[0]
action['domain'] = [('product_id', 'in', self.product_id.ids)]
@@ -13,6 +13,7 @@ class TestCoursePurchaseFlow(common.SlidesCase):
'list_price': 150,
'type': 'service',
'invoice_policy': 'order',
'is_published': True,
})
self.channel.write({
@@ -1,6 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="1">
<!-- First of all: remove admin of all memberships to avoid noisy demo data -->
<function model="slide.channel"
name="_remove_membership"
eval="[
[ref('website_slides.slide_channel_demo_0_gard_0'),
ref('website_slides.slide_channel_demo_1_gard1'),
ref('website_slides.slide_channel_demo_2_gard2'),
ref('website_slides.slide_channel_demo_3_furn0'),
ref('website_slides.slide_channel_demo_4_furn1'),
ref('website_slides.slide_channel_demo_5_furn2'),
ref('website_slides.slide_channel_demo_6_furn3')],
[ref('base.partner_admin')]
]"/>
<!-- CHANNEL 0: Basics of Gardening -->
<!-- ================================================== -->
<record id="slide_channel_0_partner_admin" model="slide.channel.partner">
+5 -11
View File
@@ -474,25 +474,19 @@ class Channel(models.Model):
for channel in self:
removed_channel_partner_domain = expression.OR([
removed_channel_partner_domain,
[('partner_id', 'in', partner_ids.ids),
[('partner_id', 'in', partner_ids),
('channel_id', '=', channel.id)]
])
if removed_channel_partner_domain:
self.env['slide.channel.partner'].sudo().search(removed_channel_partner_domain).unlink()
def action_view(self):
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'slide.channel',
'res_id': self.id,
}
def action_view_slides(self):
action = self.env.ref('website_slides.slide_slide_action').read()[0]
action['context'] = {'default_channel_id': self.id}
action['context'] = {
'search_default_published': 1,
'default_channel_id': self.id
}
action['domain'] = [('channel_id', "=", self.id), ('is_category', '=', False)]
return action
+3 -4
View File
@@ -265,7 +265,8 @@ class Slide(models.Model):
def _compute_slides_statistics(self):
# Do not use dict.fromkeys(self.ids, dict()) otherwise it will use the same dictionnary for all keys.
# Therefore, when updating the dict of one key, it updates the dict of all keys.
result = {_id: {} for _id in self.ids}
keys = ['nbr_%s' % slide_type for slide_type in self.env['slide.slide']._fields['slide_type'].get_values(self.env)]
default_vals = dict((key, 0) for key in keys)
res = self.env['slide.slide'].read_group(
[('is_published', '=', True), ('category_id', 'in', self.ids), ('is_category', '=', False)],
@@ -273,11 +274,9 @@ class Slide(models.Model):
lazy=False)
type_stats = self._compute_slides_statistics_type(res)
for cid, cdata in type_stats.items():
result[cid].update(cdata)
for record in self:
record.update(result[record.id])
record.update(type_stats.get(record._origin.id, default_vals))
def _compute_slides_statistics_type(self, read_group_res):
""" Compute statistics based on all existing slide types """
@@ -13,7 +13,7 @@ class TestUi(tests.HttpCase):
user_demo.write({
'groups_id': [(5, 0), (4, self.env.ref('base.group_user').id)]
})
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo'))
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo').ids)
self.phantom_js(
'/slides',
@@ -29,7 +29,7 @@ class TestUi(tests.HttpCase):
user_demo.write({
'groups_id': [(5, 0), (4, self.env.ref('base.group_user').id), (4, self.env.ref('website.group_website_publisher').id)]
})
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo'))
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo').ids)
self.phantom_js(
'/slides',
@@ -42,7 +42,7 @@ class TestUi(tests.HttpCase):
# group_website_designer
user_portal = self.env.ref('base.demo_user0')
user_portal.flush()
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo_portal'))
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo_portal').ids)
self.phantom_js(
'/slides',
@@ -61,7 +61,7 @@ class TestUiYoutube(tests.HttpCase):
user_demo.write({
'groups_id': [(5, 0), (4, self.env.ref('base.group_user').id)]
})
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo'))
self.env.ref('website_slides.slide_channel_demo_3_furn0')._remove_membership(self.env.ref('base.partner_demo').ids)
self.phantom_js(
'/slides',
@@ -77,7 +77,6 @@ class TestUiYoutube(tests.HttpCase):
user_demo.write({
'groups_id': [(5, 0), (4, self.env.ref('base.group_user').id), (4, self.env.ref('website.group_website_designer').id)]
})
self.env.ref('website_slides.slide_channel_demo_0_gard_0')._remove_membership(self.env.ref('base.partner_demo'))
self.phantom_js(
'/slides',
@@ -236,7 +236,7 @@
<div class="o_kanban_card_header">
<div class="o_kanban_card_header_title mb16">
<div class="o_primary">
<a name="action_view" type="object" class="mr-auto">
<a type="edit" class="mr-auto">
<span><field name="name" class="o_primary"/></span>
</a>
</div>
@@ -300,7 +300,7 @@
t-att-data-enroll="channel.enroll">
<t t-call="website_slides.slides_misc_user_image">
<t t-set="img_class" t-value="'rounded-circle mr-1'"/>
<t t-set="img_style" t-value="'height: 1.4em'"/>
<t t-set="img_style" t-value="'width: 1.4em; height: 1.4em; object-fit: cover;'"/>
</t>
<h6 class="d-flex flex-grow-1 my-0">You're enrolled</h6>
<i class="fa fa-check"/>
@@ -14,8 +14,8 @@
<div class="ml-4 ml-md-2 mt-3">
<t t-foreach="top3_users" t-as="user">
<img class="rounded-circle img-fluid shadow ml-n4"
style="border: 6px solid white; max-width: 40%"
t-attf-src="/web/image/res.users/#{user['id']}/image_128"/>
style="width: 140px; height: 140px; max-width: 40%; max-height: 40%; object-fit: cover; border: 6px solid white;"
t-attf-src="/web/image/res.users/#{user['id']}/image_256"/>
</t>
</div>
</div>
@@ -99,7 +99,7 @@
<div class="d-flex align-items-center">
<t t-call="website_slides.slides_misc_user_image">
<t t-set="img_class" t-value="'rounded-circle mr-1'"/>
<t t-set="img_style" t-value="'height: 22px'"/>
<t t-set="img_style" t-value="'width: 22px; height: 22px; object-fit: cover;'"/>
</t>
<h5 t-esc="user.name" class="d-flex flex-grow-1 mb-0"/>
<a class="d-none d-lg-block" t-att-href="'/profile/user/%s' % user.id">View</a>
@@ -451,7 +451,7 @@
<template id='slides_misc_user_image' name="User Avatar">
<img t-att-class="img_class if img_class else 'rounded-circle float-left'"
t-att-style="img_style if img_style else 'max-height: 32px;'"
t-att-style="img_style if img_style else 'width: 32px; height: 32px; object-fit: cover;'"
t-att-src="'/profile/avatar/%s?field=image_128' % user.id"
t-att-alt="user.name"/>
</template>
@@ -61,4 +61,4 @@ class SurveyUserInput(models.Model):
removed_memberships_per_partner[user_input.partner_id] = removed_memberships
for partner_id, removed_memberships in removed_memberships_per_partner.items():
removed_memberships._remove_membership(partner_id)
removed_memberships._remove_membership(partner_id.ids)
@@ -5,7 +5,7 @@
<field name="res_model">survey.survey</field>
<field name="view_mode">kanban,tree,form</field>
<field name="domain">[('certificate', '=', True)]</field>
<field name="context">{'default_certificate': True}</field>
<field name="context">{'default_certificate': True, 'default_scoring_type': 'scoring_with_answers'}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Add a new certification