[IMP] website_blog: refactoring python code for new layout

task-1973902

+ use of img locally
+ last PO review:
+ show latest in the list
+ remove underline
+ force same height if blog or not
+ ...

closes odoo/odoo#34609

Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
This commit is contained in:
Jeremy Kersten
2019-08-20 15:47:39 +00:00
parent bb0cdec459
commit 0d1fdb0b95
24 changed files with 138 additions and 196 deletions
+87 -148
View File
@@ -16,9 +16,18 @@ from odoo.tools import html2plaintext
class WebsiteBlog(http.Controller):
_blog_post_per_page = 20
_blog_post_per_page = 12 # multiple of 2,3,4
_post_comment_per_page = 10
def tags_list(self, tag_ids, current_tag):
tag_ids = list(tag_ids) # required to avoid using the same list
if current_tag in tag_ids:
tag_ids.remove(current_tag)
else:
tag_ids.append(current_tag)
tag_ids = request.env['blog.tag'].browse(tag_ids).exists()
return ','.join(slug(tag) for tag in tag_ids)
def nav_list(self, blog=None):
dom = blog and [('blog_id', '=', blog.id)] or []
if not request.env.user.has_group('website.group_website_designer'):
@@ -43,112 +52,25 @@ class WebsiteBlog(http.Controller):
return OrderedDict((year, [m for m in months]) for year, months in itertools.groupby(groups, lambda g: g['year']))
@http.route([
'/blog',
'/blog/page/<int:page>',
], type='http', auth="public", website=True)
def blogs(self, page=1, tag=None, **post):
domain = request.website.website_domain()
def _prepare_blog_values(self, blogs=False, date_begin=False, date_end=False, tags=False, state=False, page=False):
""" Prepare all values to display the blogs index page or one specific blog"""
Blog = request.env['blog.blog']
blogs = Blog.search(domain, order="create_date, id asc")
BlogPost = request.env['blog.post']
# prepare domain
domain = request.website.website_domain()
if len(blogs) == 1:
return werkzeug.utils.redirect('/blog%s' % slug(blogs[0]), code=302)
domain += [('blog_id', '=', blogs.id)]
BlogPost = request.env['blog.post']
total = BlogPost.search_count(domain)
pager = request.website.pager(
url='/blog',
total=total,
page=page,
step=self._blog_post_per_page,
)
date_begin, date_end = post.get('date_begin'), post.get('date_end')
if date_begin and date_end:
domain += [("post_date", ">=", date_begin), ("post_date", "<=", date_end)]
# retrocompatibility to accept tag as slug
active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
active_tag_ids = tags and [unslug(tag)[1] for tag in tags.split(',')] or []
if active_tag_ids:
domain += [('tag_ids', 'in', active_tag_ids)]
# function to create the string list of tag ids.
def tags_list(tag_ids, current_tag):
tag_ids = list(tag_ids) # required to avoid using the same list
if current_tag in tag_ids:
tag_ids.remove(current_tag)
else:
tag_ids.append(current_tag)
tag_ids = request.env['blog.tag'].browse(tag_ids).exists()
return ','.join(slug(tag) for tag in tag_ids)
posts = BlogPost.search(domain, offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page)
first_post = next((p for p in posts if p.website_published), None)
first_cover = first_post and json.loads(first_post.cover_properties)
blog_url = QueryURL('', ['blog', 'tag'])
return request.render("website_blog.blog_post_short", {
'posts': posts,
'pager': pager,
'blogs': blogs,
'blog_url': blog_url,
'nav_list': self.nav_list(),
'date_begin': date_begin,
'date_end': date_end,
'tags_list' : tags_list,
'active_tag_ids': active_tag_ids,
'first_post': first_post,
'first_cover': first_cover,
'blog_posts_cover_properties': [json.loads(b.cover_properties) for b in posts],
})
@http.route([
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>''',
'''/blog/<model("blog.blog"):blog>/page/<int:page>''',
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>''',
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>''',
], type='http', auth="public", website=True)
def blog(self, blog=None, tag=None, page=1, **opt):
""" Prepare all values to display the blog.
:return dict values: values for the templates, containing
- 'blog': current blog
- 'blogs': all blogs for navigation
- 'pager': pager of posts
- 'active_tag_ids' : list of active tag ids,
- 'tag': current tag, if tag_id in parameters
- 'tags_list' : function to built the comma-separated tag list ids (for the url),
- 'tags': all tags, for navigation
- 'state_info': state of published/unpublished filter
- 'nav_list': a dict [year][month] for archives navigation
- 'date': date_begin optional parameter, used in archives navigation
- 'blog_url': help object to create URLs
"""
if not blog.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
published_count, unpublished_count = 0, 0
domain = request.website.website_domain()
BlogPost = request.env['blog.post']
Blog = request.env['blog.blog']
blogs = Blog.search(domain, order="create_date asc")
# retrocompatibility to accept tag as slug
active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
if active_tag_ids:
domain += [('tag_ids', 'in', active_tag_ids)]
if blog:
domain += [('blog_id', '=', blog.id)]
if date_begin and date_end:
domain += [("post_date", ">=", date_begin), ("post_date", "<=", date_end)]
if request.env.user.has_group('website.group_website_designer'):
count_domain = domain + [("website_published", "=", True), ("post_date", "<=", fields.Datetime.now())]
published_count = BlogPost.search_count(count_domain)
@@ -161,77 +83,94 @@ class WebsiteBlog(http.Controller):
else:
domain += [("post_date", "<=", fields.Datetime.now())]
blog_url = QueryURL('', ['blog', 'tag'], blog=blog, tag=tag, date_begin=date_begin, date_end=date_end)
posts = BlogPost.search(domain, order="post_date, id asc")
total = BlogPost.search_count(domain)
posts = BlogPost.search(domain, offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page, order="post_date, id asc")
pager = request.website.pager(
url=request.httprequest.path.partition('/page/')[0],
total=total,
page=page,
step=self._blog_post_per_page,
)
first_post = next((p for p in posts if p.website_published), None)
first_cover = first_post and json.loads(first_post.cover_properties)
covers = [json.loads(b.cover_properties) for b in posts]
pager = request.website.pager(
url=request.httprequest.path.partition('/page/')[0],
total=len(posts),
page=page,
step=self._blog_post_per_page,
url_args=opt,
)
pager_begin = (page - 1) * self._blog_post_per_page
pager_end = page * self._blog_post_per_page
posts = posts[pager_begin:pager_end]
all_tags = blog.all_tags()[blog.id]
# function to create the string list of tag ids, and toggle a given one.
# used in the 'Tags Cloud' template.
def tags_list(tag_ids, current_tag):
tag_ids = list(tag_ids) # required to avoid using the same list
if current_tag in tag_ids:
tag_ids.remove(current_tag)
else:
tag_ids.append(current_tag)
tag_ids = request.env['blog.tag'].browse(tag_ids).exists()
return ','.join(slug(tag) for tag in tag_ids)
all_tags = len(blogs) == 1 and blogs.all_tags()[blogs.id] or blogs.all_tags(join=True)
tag_category = sorted(all_tags.mapped('category_id'), key=lambda category: category.name.upper())
other_tags = sorted(all_tags.filtered(lambda x: not x.category_id), key=lambda tag: tag.name.upper())
other_tags = sorted(all_tags.filtered(lambda x: not x.category_id), key=lambda tag: tag.name.upper())
values = {
'blog': blog,
'blogs': blogs,
'main_object': blog,
'other_tags': other_tags,
'state_info': {"state": state, "published": published_count, "unpublished": unpublished_count},
'active_tag_ids': active_tag_ids,
'tag': tag,
'tags_list' : tags_list,
'posts': posts,
'first_post': first_post,
'first_cover': first_cover,
'blog_posts_cover_properties': [json.loads(b.cover_properties) for b in posts],
'pager': pager,
'nav_list': self.nav_list(blog),
'blog_url': blog_url,
return {
'date_begin': date_begin,
'date_end': date_end,
'first_post': first_post,
'first_cover': first_cover,
'blog_posts_cover_properties': covers,
'other_tags': other_tags,
'tag_category': tag_category,
'nav_list': self.nav_list(),
'tags_list': self.tags_list,
'pager': pager,
'posts': posts,
'tag': tags,
'active_tag_ids': active_tag_ids,
'domain': domain,
'state_info': state and {"state": state, "published": published_count, "unpublished": unpublished_count},
'blogs': Blog.search(request.website.website_domain(), order="create_date asc, id asc"),
}
response = request.render("website_blog.blog_post_short", values)
return response
@http.route([
'/blog',
'/blog/page/<int:page>',
'/blog/tag/<string:tag>',
'/blog/tag/<string:tag>/page/<int:page>',
], type='http', auth="public", website=True)
def blogs(self, page=1, tag=None, **post):
Blog = request.env['blog.blog']
blogs = Blog.search(request.website.website_domain(), order="create_date asc, id asc")
if len(blogs) == 1:
return werkzeug.utils.redirect('/blog/%s' % slug(blogs[0]), code=302)
date_begin, date_end = post.get('date_begin'), post.get('date_end')
values = self._prepare_blog_values(blogs=blogs, date_begin=date_begin, date_end=date_end, tags=tag, page=page)
values.update({
'blog_url': QueryURL('/blog', ['tag'], date_begin=date_begin, date_end=date_end),
})
return request.render("website_blog.blog_post_short", values)
@http.route([
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>''',
'''/blog/<model("blog.blog"):blog>/page/<int:page>''',
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>''',
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>''',
], type='http', auth="public", website=True)
def blog(self, blog=None, tag=None, page=1, **opt):
if not blog.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
values = self._prepare_blog_values(blogs=blog, date_begin=date_begin, date_end=date_end, tags=tag, state=state, page=page)
values.update({
'blog_url': QueryURL('', ['blog', 'tag'], blog=blog, tag=tag, date_begin=date_begin, date_end=date_end),
'blog': blog,
})
return request.render("website_blog.blog_post_short", values)
@http.route(['''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/feed'''], type='http', auth="public")
def blog_feed(self, blog, limit='15', **kwargs):
v = {}
v['blog'] = blog
v['base_url'] = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
v['posts'] = request.env['blog.post'].search([('blog_id','=', blog.id)],
limit=min(int(limit), 50),
order="post_date DESC")
v['posts'] = request.env['blog.post'].search([('blog_id', '=', blog.id)], limit=min(int(limit), 50), order="post_date DESC")
v['html2plaintext'] = html2plaintext
r = request.render("website_blog.blog_feed", v, headers=[('Content-Type', 'application/atom+xml')])
return r
@http.route([
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
], type='http', auth="public", website=True)
def blog_post(self, blog, blog_post, tag_id=None, page=1, enable_editor=None, **post):
""" Prepare all values to display the blog.
@@ -318,7 +257,7 @@ class WebsiteBlog(http.Controller):
request.session[request.session.sid].append(blog_post.id)
# Increase counter
blog_post.sudo().write({
'visits': blog_post.visits+1,
'visits': blog_post.visits + 1,
})
return response
+22 -22
View File
@@ -35,14 +35,14 @@
<field name="is_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_1'), ref('blog_tag_2')])]"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/JgOeRuGD_Y4/1000x665')", "resize_class": "o_wblog_has_cover cover_full", "text_size_class": "o_wblog_cover_font_big", "background-color": "oe_black", "opacity": "0"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_1.png')", "resize_class": "o_wblog_has_cover cover_full", "text_size_class": "o_wblog_cover_font_big", "background-color": "oe_black", "opacity": "0"}</field>
<field name="content"><![CDATA[
<p class="lead">Sierra Tarahumara, popularly known as Copper Canyon is situated in Mexico. The area is a favorite destination among those seeking an adventurous vacation.</p>
<p>Copper Canyon is one of the six gorges in the area. Although the name suggests that the gorge might have some relevance to copper mining, this is not the case. The name is derived from the copper and green lichen covering the canyon. Copper Canyon has two climatic zones. The region features an alpine climate at the top and a subtropical climate at the lower levels. Winters are cold with frequent snowstorms at the higher altitudes. Summers are dry and hot. The capital city, Chihuahua, is a high altitude desert where weather ranges from cold winters to hot summers. The region is unique because of the various ecosystems that exist within it.</p>
<p>Another unique feature of Copper Canyon is the presence of the Tarahumara Indian culture. These semi-nomadic people live in cave dwellings. Their livelihood chiefly depends on farming and cattle ranching.</p>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/lRpTgmEsuXc/630x840" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_1_1.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by PoloX Hernandez, @elpolox</figcaption>
</figure>
@@ -53,7 +53,7 @@
<p>Several migratory and native birds, mammals and reptiles call Copper Canyon their home. The exquisite fauna in this near-pristine land is also worth checking out.</p>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/DPXytK8Z59Y/630x420" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_1_2.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Boris Smokrovic, @borisworkshop</figcaption>
</figure>
@@ -70,7 +70,7 @@
<field name="is_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_2')])]"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/PB_SdlCPCDs/1000x1000')", "resize_class": "o_wblog_has_cover cover_mid", "text_size_class": "o_wblog_cover_font_big", "background-color": "oe_blue", "opacity": "0.4"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_2.png')", "resize_class": "o_wblog_has_cover cover_mid", "text_size_class": "o_wblog_cover_font_big", "background-color": "oe_blue", "opacity": "0.4"}</field>
<field name="content"><![CDATA[
<section class="container">
<div class="row">
@@ -80,7 +80,7 @@
</div>
<div class="col-12 col-md-auto">
<figure>
<img src="https://source.unsplash.com/LK12XVJw2PQ/380x615" class="img-fluid" style="max-height:450px"/>
<img src="/website_blog/static/src/img/content_2_1.png" class="img-fluid" style="max-height:450px"/>
<figcaption class="figure-caption">Photo by Jon Ly, @jonatron</figcaption>
</figure>
</div>
@@ -89,7 +89,7 @@
<h2 class="mt-5">East Maui</h2>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/sfgH9dXcMRw/630x420" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_2_2.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Anton Repponen, @repponen</figcaption>
</figure>
@@ -99,7 +99,7 @@
<h2 class="mt-5">Molokai Maui </h2>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/guNIjIuUcgY/630x420" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_2_3.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Denys Nevozhai, @dnevozhai</figcaption>
</figure>
<p>Molokai Maui helicopter tours will take you to a different island but one that is only nine miles away and easily accessible by air. This island has a very small population with a different culture and scenery. The entire coast of the northeast is lined with cliffs and remote beaches. They are completely inaccessible by any other means of transportation than air.
@@ -117,7 +117,7 @@ People who live on the island have never even seen this remarkable scenery unles
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_1')])]"/>
<field name="website_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/yB6WFHbkX40/1000x666')", "resize_class": "o_wblog_has_cover cover_mid", "background-color": "oe_black", "opacity": "0.4"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_3.png')", "resize_class": "o_wblog_has_cover cover_mid", "background-color": "oe_black", "opacity": "0.4"}</field>
<field name="content"><![CDATA[
<p class="lead">So you’re going abroad, you’ve chosen your destination and now you have to choose a hotel.</p>
<p>Ten years ago, you’d have probably visited your local travel agent and trusted the face-to-face advice you were given by the so called ‘experts’. The 21st Century way to select and book your hotel is of course on the Internet, by using travel websites.</p>
@@ -130,7 +130,7 @@ People who live on the island have never even seen this remarkable scenery unles
</blockquote>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/76-58HpxvpQ/630x300" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_3_1.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Jason Briscoe, @jbriscoe</figcaption>
</figure>
@@ -161,14 +161,14 @@ People who live on the island have never even seen this remarkable scenery unles
</field>
</record>
<record id="blog_post_10" model="blog.post">
<record id="blog_post_4" model="blog.post">
<field name="name">How To Look Up</field>
<field name="subtitle">Be aware of this thing called “astronomy”</field>
<field name="blog_id" ref="blog_blog_2"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_5')])]"/>
<field name="website_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/zepnJQycr4U/1000x400')", "resize_class": "o_wblog_has_cover cover_mid", "text_size_class": "o_wblog_cover_font_big", "text_align_class": "text-center", "background-color": "oe_black", "opacity": "0.2"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_4.png')", "resize_class": "o_wblog_has_cover cover_mid", "text_size_class": "o_wblog_cover_font_big", "text_align_class": "text-center", "background-color": "oe_black", "opacity": "0.2"}</field>
<field name="content"><![CDATA[
<p class="lead">It is safe to say that at some point on our lives, each and every one of us has that moment when we are suddenly stunned when we come face to face with the enormity of the universe that we see in the night sky.</p>
@@ -182,7 +182,7 @@ People who live on the island have never even seen this remarkable scenery unles
<p>So it might be that once a year vacation to a camping spot or a trip to a relative’s house out in the country that we find ourselves outside when the spender of the night sky suddenly decides to put on it’s spectacular show. If you have had that kind of moment when you were literally struck breathless by the spender the night sky can show to us, you can probably remember that exact moment when you could say little else but “wow” at what you saw.</p>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/K2K1Ec_51SA/630x300" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_4_1.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Arto Marttinen, @wandervisions</figcaption>
</figure>
@@ -201,14 +201,14 @@ People who live on the island have never even seen this remarkable scenery unles
</field>
</record>
<record id="blog_post_11" model="blog.post">
<record id="blog_post_5" model="blog.post">
<field name="name">What If They Let You Run The Hubble</field>
<field name="subtitle">The beauty of astronomy is that anybody can do it.</field>
<field name="blog_id" ref="blog_blog_2"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_4'), ref('blog_tag_5')])]"/>
<field name="website_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/yZygONrUBe8/1000x400')", "resize_class": "o_wblog_has_cover cover_mid", "text_align_class": "text-center", "background-color": "oe_black", "opacity": "0.2"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_5.png')", "resize_class": "o_wblog_has_cover cover_mid", "text_align_class": "text-center", "background-color": "oe_black", "opacity": "0.2"}</field>
<field name="content"><![CDATA[
<p class="lead">From the tiniest baby to the most advanced astrophysicist, there is something for anyone who wants to enjoy astronomy. In fact, it is a science that is so accessible that virtually anybody can do it virtually anywhere they are. All they have to know how to do is to look up.</p>
<p>It really is amazing when you think about it that just by looking up on any given night, you could see virtually hundreds of thousands of stars, star systems, planets, moons, asteroids, comets and maybe a even an occasional space shuttle might wander by. It is even more breathtaking when you realize that the sky you are looking up at is for all intents and purposes the exact same sky that our ancestors hundreds and thousands of years ago enjoyed when they just looked up.</p>
@@ -221,7 +221,7 @@ People who live on the island have never even seen this remarkable scenery unles
<p>There is something timeless about the cosmos. The fact that the planets and the moon and the stars beyond them have been there for ages does something to our sense of our place in the universe. In fact, many of the stars we “see” with our naked eye are actually light that came from that star hundreds of thousands of years ago. That light is just now reaching the earth. So in a very real way, looking up is like time travel.</p>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/VBNb52J8Trk/630x300" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_5_1.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by SpaceX, @spacex</figcaption>
</figure>
@@ -244,14 +244,14 @@ People who live on the island have never even seen this remarkable scenery unles
</field>
</record>
<record id="blog_post_12" model="blog.post">
<record id="blog_post_6" model="blog.post">
<field name="name">Buying A Telescope</field>
<field name="subtitle">Before you make your first purchase…</field>
<field name="blog_id" ref="blog_blog_2"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_3'), ref('blog_tag_4')])]"/>
<field name="website_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/iPbwEiWkVMQ/1100x740')", "resize_class": "o_wblog_has_cover cover_full", "text_size_class": "o_wblog_cover_font_huge", "text_align_class": "text-center", "background-color": "oe_black", "opacity": "0.2"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_6.png')", "resize_class": "o_wblog_has_cover cover_full", "text_size_class": "o_wblog_cover_font_huge", "text_align_class": "text-center", "background-color": "oe_black", "opacity": "0.2"}</field>
<field name="content"><![CDATA[
<p class="lead">Buying the right telescope to take your love of astronomy to the next level is a big next step in the development of your passion for the stars.</p>
<p>In many ways, it is a big step from someone who is just fooling around with astronomy to a serious student of the science. But you and I both know that there is still another big step after buying a telescope before you really know how to use it.</p>
@@ -271,7 +271,7 @@ People who live on the island have never even seen this remarkable scenery unles
</ul>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/ZSrgSSGJiQs/630x300" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_6_1.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Teddy Kelley, @teddykelley</figcaption>
</figure>
@@ -285,14 +285,14 @@ People who live on the island have never even seen this remarkable scenery unles
</field>
</record>
<record id="blog_post_13" model="blog.post">
<record id="blog_post_7" model="blog.post">
<field name="name">Beyond The Eye</field>
<field name="subtitle">Becoming part of the society of devoted amateur astronomers.</field>
<field name="blog_id" ref="blog_blog_2"/>
<field name="tag_ids" eval="[(6, 0, [ref('blog_tag_5')])]"/>
<field name="website_published" eval="True"/>
<field name="published_date" eval="time.strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="cover_properties">{"background-image": "url('https://source.unsplash.com/vB6mPiQId88/1109x739')", "resize_class": "o_wblog_has_cover cover_mid", "text_size_class": "o_wblog_cover_font_big", "background-color": "oe_black", "opacity": "0.4"}</field>
<field name="cover_properties">{"background-image": "url('/website_blog/static/src/img/cover_7.png')", "resize_class": "o_wblog_has_cover cover_mid", "text_size_class": "o_wblog_cover_font_big", "background-color": "oe_black", "opacity": "0.4"}</field>
<field name="content"><![CDATA[
<p class="lead">For many of us, our very first experience of learning about the celestial bodies begins when we saw our first full moon in the sky. It is truly a magnificent view even to the naked eye.</p>
<p>If the night is clear, you can see amazing detail of the lunar surface just star gazing on in your back yard.
@@ -309,7 +309,7 @@ Naturally, as you grow in your love of astronomy, you will find many celestial b
<p>The best time to view the moon, obviously, is at night when there are few clouds and the weather is accommodating for a long and lasting study. The first quarter yields the greatest detail of study. And don’t be fooled but the blotting out of part of the moon when it is not in full moon stage. The phenomenon known as “earthshine” gives you the ability to see the darkened part of the moon with some detail as well, even if the moon is only at quarter or half display.</p>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/ZMxbPRIPtWE/630x300" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_7_1.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Patrick Brinksma, @patrickbrinksma</figcaption>
</figure>
@@ -317,7 +317,7 @@ Naturally, as you grow in your love of astronomy, you will find many celestial b
<p>Of course, to take your moon worship to the ultimate, stepping your equipment up to a good starter telescope will give you the most stunning detail of the lunar surface. With each of these upgrades your knowledge and the depth and scope of what you will be able to see will improve geometrically. For many amateur astronomers, we sometimes cannot get enough of what we can see on this our closest space object. </p>
<figure class="mt-2 mb-4">
<img src="https://source.unsplash.com/oMpAz-DN-9I/630x300" class="img-fluid w-100"/>
<img src="/website_blog/static/src/img/content_7_2.png" class="img-fluid w-100"/>
<figcaption class="figure-caption">Photo by Greg Rakozy, @grakozy</figcaption>
</figure>
+12 -5
View File
@@ -5,13 +5,12 @@ from datetime import datetime
import random
import json
import itertools
from odoo import api, models, fields, _
from odoo.addons.http_routing.models.ir_http import slug
from odoo.tools.translate import html_translate
from odoo.tools import html2plaintext
class Blog(models.Model):
_name = 'blog.blog'
_description = 'Blogs'
@@ -48,7 +47,8 @@ class Blog(models.Model):
subtype = 'mail.mt_note'
return super(Blog, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs)
def all_tags(self, min_limit=1):
def all_tags(self, join=False, min_limit=1):
BlogTag = self.env['blog.tag']
req = """
SELECT
p.blog_id, count(*), r.blog_tag_id
@@ -65,13 +65,20 @@ class Blog(models.Model):
"""
self._cr.execute(req, [tuple(self.ids)])
tag_by_blog = {i.id: [] for i in self}
all_tags = set()
for blog_id, freq, tag_id in self._cr.fetchall():
if freq >= min_limit:
tag_by_blog[blog_id].append(tag_id)
if join:
all_tags.add(tag_id)
else:
tag_by_blog[blog_id].append(tag_id)
if join:
return BlogTag.browse(all_tags)
BlogTag = self.env['blog.tag']
for blog_id in tag_by_blog:
tag_by_blog[blog_id] = BlogTag.browse(tag_by_blog[blog_id])
return tag_by_blog
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

@@ -15,7 +15,7 @@
<a href="/blog" t-attf-class="nav-link font-weight-bold #{(not blog) and 'active'}">All</a>
</li>
<li class="nav-item" t-foreach="blogs" t-as="nav_blog">
<a t-attf-href="#{blog_url(blog=nav_blog, tag=False, date_begin=False, date_end=False)}"
<a t-attf-href="/blog/#{slug(nav_blog)}"
t-attf-class="nav-link #{(blog and nav_blog.id == blog.id and (not tag and not date_begin)) and 'active' or ''}">
<b t-field="nav_blog.name"/>
</a>
@@ -169,8 +169,6 @@ Options:
<template id="opt_sidebar_blog_index_tags" name="Tags List" priority="3" inherit_id="website_blog.sidebar_blog_index" active="True" customize_show="True">
<xpath expr="//div[@id='o_wblog_sidebar']" position="inside">
<!-- TO FIX: This template works if a single blog is provided only -->
<!-- ==============================================================-->
<div t-if="other_tags or tag_category" class="o_wblog_sidebar_block pb-5">
<h6 class="text-uppercase pb-2 mb-4 border-bottom font-weight-bold">Tags</h6>
<div class="h5">
@@ -47,11 +47,13 @@ according to the enabled options.
</div>
</div>
</div>
<div t-if="posts" t-attf-class="row #{'mx-n2' if not opt_blog_readable else ''}">
<!-- If posts are filtered by date or tags, show applyed filters -->
<div t-if="tag or date_begin" class="col-12 mb-3">
<div class="h4 mb-3"> <t t-esc="len(posts)"/> Articles</div>
<div class="h4 mb-3"> <t t-esc="len(posts)"/>
<t t-if="len(posts) &lt; 2">Article</t>
<t t-else="">Articles</t>
</div>
<t t-if="tag">
<!-- Show active tags with a category set -->
<t t-foreach="tag_category" t-as="nav_tag_category">
@@ -60,6 +62,7 @@ according to the enabled options.
<t t-set='dismissibleBtn' t-value="True"/>
</t>
</t>
<!-- Show active tags without a category set -->
<t t-call="website_blog.tags_list">
<t t-set='tags' t-value='other_tags'/>
@@ -75,7 +78,7 @@ according to the enabled options.
</div>
<!-- Loop through posts: exclude the first one if already displayed as top banner -->
<t t-foreach="posts" t-as="blog_post" t-if="not (posts_list_exclude_first and blog_post.id == first_post.id)">
<t t-foreach="posts" t-as="blog_post">
<!-- Define 'colWidth' qWeb variable, to be assigned later.
Adjust accordingly if sidebar and/or readability modes are active. -->
@@ -104,7 +107,7 @@ according to the enabled options.
<t t-call="website_blog.post_cover_image"/>
</div>
<div t-if="opt_posts_loop_show_teaser" t-att-class="opt_blog_cards_design and 'card-body pt-0'">
<t t-call="website_blog.post_excerpt"/>
<t t-call="website_blog.post_teaser"/>
</div>
<div t-if="opt_blog_cards_design" t-attf-class="opt_blog_cards_design and 'card-body pt-0 pb-2'}">
<t t-call="website_blog.post_info"></t>
@@ -121,7 +124,7 @@ according to the enabled options.
<div t-att-class="opt_blog_cards_design and 'card-body px-2 py-0 mb-2'">
<t t-call="website_blog.post_heading"/>
<div t-if="opt_posts_loop_show_teaser">
<t t-call="website_blog.post_excerpt"/>
<t t-call="website_blog.post_teaser"/>
</div>
</div>
<div t-attf-class="o_wblog_normalize_font #{opt_blog_cards_design and 'card-footer px-2 pb-2'">
@@ -144,7 +147,7 @@ according to the enabled options.
<template id="post_heading">
<a t-attf-href="/blog/#{slug(blog_post.blog_id)}/post/#{slug(blog_post)}"
t-field="blog_post.name"
t-attf-class="d-block text-reset o_blog_post_title my-0 #{'h3' if opt_blog_list_view else ('h5' if opt_blog_readable else 'h6')}">
t-attf-class="d-block text-reset text-decoration-none o_blog_post_title my-0 #{'h3' if opt_blog_list_view else ('h5' if opt_blog_readable else 'h6')}">
Untitled Post
</a>
@@ -156,7 +159,7 @@ according to the enabled options.
<!-- ====== Sub-Template: Posts list : Posts Info ======================= -->
<template id="post_info">
<div class="d-flex small flex-wrap mb-1 w-100">
<div t-attf-class="d-flex flex-wrap align-items-center justify-content-between mx-n2 #{'flex-grow-0 w-auto mw-100' if opt_blog_list_view else 'flex-grow-1' }">
<div t-attf-class="d-flex flex-wrap align-items-center justify-content-between mx-n2 #{opt_blog_list_view and 'flex-grow-0 w-auto mw-100' or 'flex-grow-1' }">
<time t-field="blog_post.post_date" class="text-nowrap font-weight-bold px-2" t-options='{"format": "MMM d, yyyy"}'/>
<div t-if="opt_posts_loop_show_stats" class="px-2">
<b class="text-nowrap"><i class="fa fa-comment text-muted mr-1"/><t t-esc="len(blog_post.message_ids)"/></b>
@@ -181,7 +184,7 @@ according to the enabled options.
<t t-call="website_blog.blog_cover">
<t t-set="cover_properties" t-value="blog_posts_cover_properties[blog_post_index]"/>
<t t-set="additionnal_classes" t-value="'o_list_cover' + (' rounded overflow-hidden shadow mb-3' if not opt_blog_cards_design else '' )"/>
<t t-set="additionnal_classes" t-value="'o_list_cover' + (not opt_blog_cards_design and ' rounded overflow-hidden shadow mb-3' or '')"/>
<t t-if="opt_posts_loop_show_author" t-call="website_blog.post_author">
<t t-set="additionnal_classes" t-value="'o_wblog_post_list_author o_list_cover d-flex text-white w-100 ' + ('p-3 h5 m-0' if opt_blog_list_view else 'px-2 pb-2 pt-3') "/>
@@ -191,9 +194,9 @@ according to the enabled options.
</a>
</template>
<!-- ====== Sub-Template: Posts list : Posts Excerpt + Tags ============= -->
<template id="post_excerpt">
<a t-attf-href="/blog/#{slug(blog_post.blog_id)}/post/#{slug(blog_post)}" class="text-reset" t-att-title="blog_post.name">
<!-- ====== Sub-Template: Posts list : Posts Teaser + Tags ============= -->
<template id="post_teaser">
<a t-attf-href="/blog/#{slug(blog_post.blog_id)}/post/#{slug(blog_post)}" class="text-reset text-decoration-none">
<div t-if="opt_blog_list_view" t-field="blog_post.teaser" class="mt-2 o_wblog_read_text"/>
<div t-else="" t-field="blog_post.teaser" t-attf-class="mt-2 #{opt_blog_readable and 'o_wblog_normalize_font'}"/>
</a>
@@ -112,13 +112,7 @@ list of filtered posts (by date or tag).
-->
<template id="opt_blog_cover_post" name="Top banner - Show Latest Post" inherit_id="website_blog.blog_post_short" active="True" customize_show="True">
<xpath expr="//div[@id='o_wblog_blog_top_droppable']" position="replace">
<!-- In the general post list, exclude the first post since it will be shown here -->
<t t-set="posts_list_exclude_first" t-value="True"/>
<!-- Check if 'opt_blog_cover_post_show_author' option is enabled -->
<t t-set="opt_blog_cover_post_show_author" t-value="request.website.viewref('website_blog.opt_blog_cover_post_show_author').active"/>
<t t-set="blog_post" t-value="first_post"/>
<div class="container">
@@ -178,11 +172,12 @@ list of filtered posts (by date or tag).
<div t-field="blog_post.subtitle" id="o_wblog_post_subtitle" placeholder="Subtitle"/>
</a>
<div t-if="not blog or opt_blog_cover_post_show_author">
<div>
<span t-if="not blog" class="text-white small mt-2 mb-3">
in <i class="fa fa-folder-open text-white-75"/><a t-attf-href="#{blog_url(blog=blog_post.blog_id)}" class="text-white" t-field="blog_post.blog_id"/>
<t t-if="opt_blog_cover_post_show_author">, </t>
</span>
<span t-else="">&amp;nbsp;</span>
<small t-if="opt_blog_cover_post_show_author">
by
<t t-if="opt_blog_cover_post_show_author" t-call="website_blog.post_author">