trytond-galatea_cms/cms.py

492 lines
18 KiB
Python
Raw Permalink Normal View History

2014-11-07 14:24:08 +01:00
# This file is part galatea_cms module for Tryton.
# The COPYRIGHT file at the top level of this repository contains
# the full copyright notices and license terms.
2021-09-10 12:13:32 +02:00
from trytond.model import ModelSQL, ModelView, DeactivableMixin, fields, tree
from trytond.pool import Pool
from trytond.pyson import Bool, Equal, Eval, In, Not
from trytond import backend
from trytond.i18n import gettext
2018-01-24 17:34:16 +01:00
from trytond.modules.galatea.resource import GalateaVisiblePage
from trytond.modules.galatea.tools import slugify
from trytond.transaction import Transaction
from .exceptions import DeleteWarning
2014-05-27 09:45:10 +02:00
__all__ = ['Menu', 'Article', 'ArticleBlock', 'ArticleWebsite', 'Block',
2015-02-02 16:57:31 +01:00
'Carousel', 'CarouselItem']
_TITLE_STYLE = [
(None, ''),
('h1', 'H1'),
('h2', 'H2'),
('h3', 'H3'),
('h4', 'H4'),
('h5', 'H5'),
('h6', 'H6'),
]
_BLOCK_TYPES = [
('image', 'Image'),
('remote_image', 'Remote Image'),
('custom_code', 'Custom Code'),
('section_description', 'Section Description'),
2020-01-14 12:42:15 +01:00
('image_bkg', 'Image Background'),
('section_description_black', 'Section Description Black'),
('css_bkg', 'CSS Background'),
('section_home_background', 'Section Home Background'),
('image_top_bkg', 'Image Top Background'),
('section_description_blue', 'Section Description Blue'),
('section_description_icon', 'Section Description Icon'),
('section_title', 'Section Title'),
]
_BLOCK_COVER_IMAGE_DOMAIN = [
2018-01-24 17:34:16 +01:00
('resource', '=', Eval('attachment_resource')),
]
_BLOCK_COVER_IMAGE_STATES = {
2018-01-24 17:34:16 +01:00
'readonly': Eval('id', 0) <= 0,
}
_BLOCK_COVER_IMAGE_CONTEXT = {
2018-01-24 17:34:16 +01:00
'resource': Eval('attachment_resource'),
}
2018-01-24 17:34:16 +01:00
_BLOCK_COVER_IMAGE_DEPENDS = ['attachment_resource']
_BLOCK_TITLE_REQUIRED = ['section_description']
2014-05-27 09:45:10 +02:00
2021-09-10 12:13:32 +02:00
class Menu(tree(), DeactivableMixin, ModelSQL, ModelView):
2014-05-27 09:45:10 +02:00
"Menu CMS"
__name__ = 'galatea.cms.menu'
# _rec_name = 'name_used'
2016-04-14 18:16:02 +02:00
website = fields.Many2One('galatea.website', 'Website',
ondelete='RESTRICT', required=True)
2015-02-02 16:57:31 +01:00
name = fields.Char('Name', translate=True, states={
'readonly': Eval('name_uri', False),
}, depends=['name_uri'])
name_uri = fields.Boolean('Use URI\'s name', states={
'invisible': ((Eval('target_type', '') != 'internal_uri')
| ~Bool(Eval('target_uri'))),
}, depends=['target_type', 'target_uri'])
2015-02-02 16:57:31 +01:00
name_used = fields.Function(fields.Char('Name', translate=True,
required=True),
'on_change_with_name', searcher='search_name_used')
2014-05-27 09:45:10 +02:00
code = fields.Char('Code', required=True,
help='Internal code.')
2015-02-02 16:57:31 +01:00
target_type = fields.Selection([
('internal_uri', 'Internal URI'),
('external_url', 'External URL'),
], 'Type', required=True)
target_uri = fields.Many2One('galatea.uri', 'Target URI', states={
'invisible': Eval('target_type', '') != 'internal_uri',
2016-04-14 18:16:02 +02:00
}, domain=[
('website', '=', Eval('website')),
], depends=['target_uri', 'website'])
2015-02-02 16:57:31 +01:00
target_url = fields.Char('Target URL', states={
'invisible': Eval('target_type', '') != 'external_url',
2015-02-02 16:57:31 +01:00
}, depends=['target_type'])
2015-02-23 20:25:30 +01:00
url = fields.Function(fields.Char('URL'),
'get_url')
2016-04-14 18:16:02 +02:00
parent = fields.Many2One('galatea.cms.menu', 'Parent', domain=[
('website', '=', Eval('website')),
], depends=['website'])
left = fields.Integer('Left', required=True)
right = fields.Integer('Right', required=True)
2014-05-27 09:45:10 +02:00
childs = fields.One2Many('galatea.cms.menu', 'parent', 'Children')
sequence = fields.Integer('Sequence')
2014-06-27 11:52:20 +02:00
nofollow = fields.Boolean('Nofollow',
help='Add attribute in links to not search engines continue')
2016-04-14 18:16:02 +02:00
# TODO: I think the following fields should go to another module
2015-02-02 16:57:31 +01:00
css = fields.Char('CSS',
help='Class CSS in menu.')
icon = fields.Char('Icon',
help='Icon name show in menu.')
login = fields.Boolean('Login', help='Allow login users')
manager = fields.Boolean('Manager', help='Allow manager users')
2016-07-29 12:06:12 +02:00
hidden_xs = fields.Boolean('Hidden XS',
help='Hidden Extra small devices')
hidden_sm = fields.Boolean('Hidden SM',
help='Hidden Small devices')
hidden_md = fields.Boolean('Hidden MD',
help='Hidden Medium devices')
hidden_lg = fields.Boolean('Hidden LG',
help='Hidden Large devices')
2019-12-05 15:34:11 +01:00
image = fields.Many2One('ir.attachment', 'Image',
domain=[
('resource.id', '=', Eval('id', -1), 'galatea.cms.menu')
2019-12-10 09:44:31 +01:00
], depends = ['id'])
2020-01-10 14:56:11 +01:00
description = fields.Text('Description', translate=True)
2014-05-27 09:45:10 +02:00
2015-02-23 20:25:30 +01:00
@classmethod
def __setup__(cls):
super(Menu, cls).__setup__()
cls._order = [
('sequence', 'ASC'),
('id', 'ASC'),
]
2015-02-23 20:25:30 +01:00
2015-02-02 16:57:31 +01:00
@fields.depends('name_uri', 'target_uri', 'name')
def on_change_with_name(self, name=None):
return (self.target_uri.name if self.name_uri and self.target_uri
2015-02-02 16:57:31 +01:00
else self.name)
2014-05-27 09:45:10 +02:00
def get_rec_name(self, name):
if self.name_used:
return self.name_used
return '(%s)' % self.id
@classmethod
def search_name_used(cls, name, clause):
return [
['OR', [
('name_uri', '=', True),
('target_uri.name',) + tuple(clause[1:]),
], [
('name_uri', '=', False),
('name',) + tuple(clause[1:]),
]],
]
2015-02-23 20:25:30 +01:00
def get_url(self, name):
return (self.target_url if self.target_type == 'external_url'
else (self.target_uri.uri if self.target_uri else '#'))
2016-07-14 15:39:29 +02:00
@classmethod
def default_website(cls):
Website = Pool().get('galatea.website')
websites = Website.search([])
if len(websites) == 1:
return websites[0].id
2014-05-27 09:45:10 +02:00
@staticmethod
def default_left():
return 0
@staticmethod
def default_right():
return 0
@staticmethod
def default_sequence():
return 1
@classmethod
def copy(cls, menus, default=None):
if default is None:
default = {}
default['left'] = 0
default['right'] = 0
2015-02-02 16:57:31 +01:00
# new_menus = []
# for menu in menus:
# new_menu, = super(Menu, cls).copy([menu], default=default)
# new_menus.append(new_menu)
# return new_menus
return super(Menu, cls).copy(menus, default=default)
2014-05-27 09:45:10 +02:00
2018-01-24 17:34:16 +01:00
class Article(GalateaVisiblePage):
2014-05-27 09:45:10 +02:00
"Article CMS"
__name__ = 'galatea.cms.article'
2018-01-24 17:34:16 +01:00
_table = None # Needed to reset GalateaVisiblePage._table
2015-02-02 16:57:31 +01:00
websites = fields.Many2Many('galatea.cms.article-galatea.website',
'article', 'website', 'Websites', required=True)
2016-07-18 15:59:58 +02:00
description = fields.Text('Description', translate=True,
help='You could write wiki or RST markups to create html content.')
description_html = fields.Function(fields.Text('Description HTML'),
'on_change_with_description_html')
2015-02-02 16:57:31 +01:00
markup = fields.Selection([
(None, ''),
('wikimedia', 'WikiMedia'),
('rest', 'ReStructuredText'),
], 'Markup')
metadescription = fields.Char('Meta Description', translate=True,
help='Almost all search engines recommend it to be shorter '
2014-05-27 09:45:10 +02:00
'than 155 characters of plain text')
2015-02-02 16:57:31 +01:00
metakeywords = fields.Char('Meta Keywords', translate=True,
2014-05-27 09:45:10 +02:00
help='Separated by comma')
2015-02-02 16:57:31 +01:00
metatitle = fields.Char('Meta Title', translate=True)
attachments = fields.One2Many('ir.attachment', 'resource', 'Attachments')
blocks = fields.One2Many('galatea.cms.article.block', 'article', 'Blocks')
show_title = fields.Boolean('Show Title')
2014-05-27 09:45:10 +02:00
@classmethod
def __setup__(cls):
super(Article, cls).__setup__()
domain_clause = ('allowed_models.model', 'in', ['galatea.cms.article'])
if domain_clause not in cls.template.domain:
cls.template.domain.append(domain_clause)
@classmethod
def __register__(cls, module_name):
2019-12-10 10:49:53 +01:00
table = backend.TableHandler(cls, module_name)
super(Article, cls).__register__(module_name)
table.not_null_action('galatea_website', action='remove')
table.not_null_action('template', action='remove')
@classmethod
def view_attributes(cls):
return super(Article, cls).view_attributes() + [
('//page[@id="descriptions"]/group[@id="description_html"]', 'states', {
'invisible': Eval('markup'),
}),
('//page[@id="descriptions"]/group[@id="description"]', 'states', {
'invisible': ~Eval('markup'),
}),
]
2015-02-02 16:57:31 +01:00
@classmethod
def default_websites(cls):
Website = Pool().get('galatea.website')
websites = Website.search([('active', '=', True)])
return [w.id for w in websites]
2016-07-29 09:47:12 +02:00
@staticmethod
def default_show_title():
return True
@fields.depends('description')
def on_change_with_description_html(self, name=None):
if self.description:
return self.description
2015-02-02 16:57:31 +01:00
@classmethod
def calc_uri_vals(cls, record_vals):
# TODO: calc parent and template?
uri_vals = super(Article, cls).calc_uri_vals(record_vals)
if 'template' in record_vals:
uri_vals['template'] = record_vals['template']
return uri_vals
2015-02-02 16:57:31 +01:00
@classmethod
def delete(cls, articles):
2020-05-05 16:15:01 +02:00
Warning = Pool().get('res.user.warning')
key = 'delete_articles'
if Warning.check(key):
raise DeleteWarning(key,
gettext('galatea_cms.msg_delete_articles'))
2015-02-19 10:23:35 +01:00
super(Article, cls).delete(articles)
2015-02-02 16:57:31 +01:00
@property
def slug_langs(self):
'''Return dict slugs by llanguage'''
pool = Pool()
Lang = pool.get('ir.lang')
langs = Lang.search([
('active', '=', True),
('translatable', '=', True),
])
slugs = {}
for lang in langs:
with Transaction().set_context(language=lang.code):
slugs[lang.code] = self.__class__(self.id).slug
return slugs
2015-02-02 16:57:31 +01:00
class ArticleBlock(ModelSQL, ModelView):
"Article Block CMS"
__name__ = 'galatea.cms.article.block'
article = fields.Many2One('galatea.cms.article', 'Article',
required=True)
block = fields.Many2One('galatea.cms.block', 'Block',
required=True)
sequence = fields.Integer('Sequence')
@staticmethod
def default_sequence():
return 1
2016-07-15 13:23:45 +02:00
@classmethod
def __setup__(cls):
super(ArticleBlock, cls).__setup__()
cls._order.insert(0, ('sequence', 'ASC'))
2015-02-02 16:57:31 +01:00
class ArticleWebsite(ModelSQL):
'Galatea CMS Article - Website'
__name__ = 'galatea.cms.article-galatea.website'
article = fields.Many2One('galatea.cms.article', 'Article',
ondelete='CASCADE', required=True)
2015-02-02 16:57:31 +01:00
website = fields.Many2One('galatea.website', 'Website',
ondelete='RESTRICT', required=True)
2014-07-16 11:54:17 +02:00
2014-05-27 09:45:10 +02:00
2021-09-10 12:13:32 +02:00
class Block(DeactivableMixin, ModelSQL, ModelView):
2014-05-27 09:45:10 +02:00
"Block CMS"
__name__ = 'galatea.cms.block'
name = fields.Char('Name', required=True)
2014-05-27 09:45:10 +02:00
code = fields.Char('Code', required=True, help='Internal code.')
type = fields.Selection(_BLOCK_TYPES, 'Type', required=True)
2015-02-02 16:57:31 +01:00
file = fields.Many2One('galatea.static.file', 'File', states={
2014-05-27 09:45:10 +02:00
'required': Equal(Eval('type'), 'image'),
'invisible': Not(Equal(Eval('type'), 'image'))
})
2015-02-02 16:57:31 +01:00
remote_image_url = fields.Char('Remote Image URL', states={
2014-05-27 09:45:10 +02:00
'required': Equal(Eval('type'), 'remote_image'),
'invisible': Not(Equal(Eval('type'), 'remote_image'))
})
custom_code = fields.Text('Custom Code', translate=True,
states={
'required': Equal(Eval('type'), 'custom_code'),
'invisible': Not(Equal(Eval('type'), 'custom_code'))
},
2016-07-18 15:59:58 +02:00
help='You could write wiki or RST markups to create html content.')
2014-05-27 09:45:10 +02:00
height = fields.Integer('Height',
2018-01-24 17:34:16 +01:00
states={
2014-05-27 09:45:10 +02:00
'invisible': Not(In(Eval('type'), ['image', 'remote_image']))
})
width = fields.Integer('Width',
2018-01-24 17:34:16 +01:00
states={
2014-05-27 09:45:10 +02:00
'invisible': Not(In(Eval('type'), ['image', 'remote_image']))
})
alternative_text = fields.Char('Alternative Text',
2018-01-24 17:34:16 +01:00
states={
2014-05-27 09:45:10 +02:00
'invisible': Not(In(Eval('type'), ['image', 'remote_image']))
})
click_url = fields.Char('Click URL', translate=True,
2018-01-24 17:34:16 +01:00
states={
2014-05-27 09:45:10 +02:00
'invisible': Not(In(Eval('type'), ['image', 'remote_image']))
})
attachments = fields.One2Many('ir.attachment', 'resource', 'Attachments')
2015-01-15 11:19:05 +01:00
visibility = fields.Selection([
2018-01-24 17:34:16 +01:00
('public', 'Public'),
('register', 'Register'),
('manager', 'Manager'),
2015-01-15 11:19:05 +01:00
], 'Visibility', required=True)
2016-07-20 17:17:06 +02:00
css = fields.Char('CSS',
help='Seperated styles by a space')
title = fields.Char('Title', translate=True,
states={
'required': Eval('type').in_(_BLOCK_TITLE_REQUIRED),
}, depends=['type'])
title_headings = fields.Selection(_TITLE_STYLE, 'Title Headings')
show_title = fields.Boolean('Show Title')
paragraph1 = fields.Text('Paragraph 1', translate=True)
paragraph2 = fields.Text('Paragraph 2', translate=True)
paragraph3 = fields.Text('Paragraph 3', translate=True)
paragraph4 = fields.Text('Paragraph 4', translate=True)
paragraph5 = fields.Text('Paragraph 5', translate=True)
markup = fields.Selection([
(None, ''),
('wikimedia', 'WikiMedia'),
('rest', 'ReStructuredText'),
], 'Markup')
attachment_resource = fields.Function(fields.Char('Attachment Resource'),
'get_attachment_resource')
cover_image1 = fields.Many2One('ir.attachment', 'Cover Image 1',
domain=_BLOCK_COVER_IMAGE_DOMAIN,
states=_BLOCK_COVER_IMAGE_STATES,
context=_BLOCK_COVER_IMAGE_CONTEXT,
depends=_BLOCK_COVER_IMAGE_DEPENDS)
cover_image2 = fields.Many2One('ir.attachment', 'Cover Image 2',
domain=_BLOCK_COVER_IMAGE_DOMAIN,
states=_BLOCK_COVER_IMAGE_STATES,
context=_BLOCK_COVER_IMAGE_CONTEXT,
depends=_BLOCK_COVER_IMAGE_DEPENDS)
cover_image3 = fields.Many2One('ir.attachment', 'Cover Image 3',
domain=_BLOCK_COVER_IMAGE_DOMAIN,
states=_BLOCK_COVER_IMAGE_STATES,
context=_BLOCK_COVER_IMAGE_CONTEXT,
depends=_BLOCK_COVER_IMAGE_DEPENDS)
cover_image4 = fields.Many2One('ir.attachment', 'Cover Image 4',
domain=_BLOCK_COVER_IMAGE_DOMAIN,
states=_BLOCK_COVER_IMAGE_STATES,
context=_BLOCK_COVER_IMAGE_CONTEXT,
depends=_BLOCK_COVER_IMAGE_DEPENDS)
cover_image5 = fields.Many2One('ir.attachment', 'Cover Image 5',
domain=_BLOCK_COVER_IMAGE_DOMAIN,
states=_BLOCK_COVER_IMAGE_STATES,
context=_BLOCK_COVER_IMAGE_CONTEXT,
depends=_BLOCK_COVER_IMAGE_DEPENDS)
cover_image_align = fields.Selection([
(None, 'None'),
2018-01-24 17:34:16 +01:00
('top', 'Top'),
('bottom', 'Bottom'),
('right', 'Right'),
('left', 'Left'),
], 'Cover Image Align')
total_cover_images = fields.Function(fields.Integer('Total Cover Images'),
'on_change_with_total_cover_images')
2014-05-27 09:45:10 +02:00
@staticmethod
def default_type():
return 'custom_code'
2014-05-27 09:45:10 +02:00
2015-01-15 11:19:05 +01:00
@staticmethod
def default_visibility():
return 'public'
@staticmethod
def default_show_title():
return True
@fields.depends('name', 'code')
2014-05-27 09:45:10 +02:00
def on_change_name(self):
if self.name and not self.code:
self.code = slugify(self.name)
2014-06-12 19:37:16 +02:00
@fields.depends('cover_image1', 'cover_image2', 'cover_image3',
'cover_image4', 'cover_image5')
def on_change_with_total_cover_images(self, name=None):
total = 0
if self.cover_image1:
total += 1
if self.cover_image2:
total += 1
if self.cover_image3:
total += 1
if self.cover_image4:
total += 1
if self.cover_image5:
total += 1
return total
def get_attachment_resource(self, name):
if self.id:
return 'galatea.cms.block,%s' % self.id
return 'galatea.cms.block,-1'
2014-06-12 19:37:16 +02:00
2021-09-10 12:13:32 +02:00
class Carousel(DeactivableMixin, ModelSQL, ModelView):
2014-06-12 19:37:16 +02:00
"Carousel CMS"
__name__ = 'galatea.cms.carousel'
name = fields.Char('Name', translate=True, required=True)
2014-06-12 19:37:16 +02:00
code = fields.Char('Code', required=True,
help='Internal code. Use characters az09')
items = fields.One2Many('galatea.cms.carousel.item', 'carousel', 'Items')
2018-01-24 17:34:16 +01:00
@fields.depends('name', 'code')
2014-06-12 19:37:16 +02:00
def on_change_name(self):
if self.name and not self.code:
self.code = slugify(self.name)
2014-06-12 19:37:16 +02:00
2021-09-10 12:13:32 +02:00
class CarouselItem(DeactivableMixin, ModelSQL, ModelView):
2014-06-12 19:37:16 +02:00
"Carousel Item CMS"
__name__ = 'galatea.cms.carousel.item'
2018-01-24 17:34:16 +01:00
carousel = fields.Many2One(
'galatea.cms.carousel', "Carousel", required=True)
2014-06-12 19:37:16 +02:00
name = fields.Char('Label', translate=True, required=True)
link = fields.Char('Link', translate=True,
help='URL absolute')
image = fields.Char('Image', translate=True,
help='Image with URL absolute')
sublabel = fields.Char('Sublabel', translate=True,
help='In case text carousel, second text')
description = fields.Char('Description', translate=True,
help='In cas text carousel, description text')
html = fields.Text('HTML', translate=True,
help='HTML formated item - Content carousel-inner')
sequence = fields.Integer('Sequence')
@staticmethod
def default_sequence():
return 1
@classmethod
def __setup__(cls):
super(CarouselItem, cls).__setup__()
cls._order = [
('sequence', 'ASC'),
('id', 'ASC'),
]