Skip to content

Commit 390d5bf

Browse files
committed
[ADD] Chapter 2: Create a gallery view - point 1
[ADD] Chapter 2: Create a galler view - points 2 to 14 [FIX] runbot correction in validation.py [FIX] real fix in validation.py
1 parent 0198081 commit 390d5bf

File tree

16 files changed

+431
-5
lines changed

16 files changed

+431
-5
lines changed

awesome_clicker/static/src/clicker_migration.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const migrations = [
1313
apply: (state) => {
1414
console.log("New tree available: peach tree!");
1515
state.trees.peachTree = {
16-
price: 1000000,
16+
price: 1500000,
1717
number: 0,
1818
level: 4,
1919
fruit: "peaches"

awesome_gallery/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# -*- coding: utf-8 -*-
22
from . import models
3+
from . import validation

awesome_gallery/models/ir_ui_view.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ class View(models.Model):
66
_inherit = 'ir.ui.view'
77

88
type = fields.Selection(selection_add=[('gallery', "Awesome Gallery")])
9+
10+
def _is_qweb_based_view(self, view_type):
11+
return view_type == "gallery" or super()._is_qweb_based_view(view_type)
12+
13+
def _get_view_info(self):
14+
return {'gallery': {'icon': 'fa fa-picture-o'}} | super()._get_view_info()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0"
3+
xmlns:a="http://relaxng.org/ns/annotation/1.0"
4+
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
5+
<!-- Handling of element overloading when inheriting from a base
6+
template
7+
-->
8+
<rng:define name="gallery">
9+
<rng:element name="gallery">
10+
11+
<rng:attribute name="image_field" />
12+
13+
<rng:optional>
14+
<rng:attribute name="limit"/>
15+
</rng:optional>
16+
17+
<rng:zeroOrMore>
18+
<rng:element name="field">
19+
<rng:attribute name="name"/>
20+
</rng:element>
21+
</rng:zeroOrMore>
22+
23+
<rng:optional>
24+
<rng:ref name="tooltip-template"/>
25+
</rng:optional>
26+
27+
</rng:element>
28+
</rng:define>
29+
30+
<rng:define name="tooltip-template">
31+
<rng:element name="tooltip-template">
32+
<rng:zeroOrMore>
33+
<rng:text/>
34+
<rng:ref name="any"/>
35+
</rng:zeroOrMore>
36+
</rng:element>
37+
</rng:define>
38+
39+
<rng:define name="any">
40+
<rng:element>
41+
<rng:anyName/>
42+
<rng:zeroOrMore>
43+
<rng:choice>
44+
<rng:attribute>
45+
<rng:anyName/>
46+
</rng:attribute>
47+
<rng:text/>
48+
<rng:ref name="any"/>
49+
</rng:choice>
50+
</rng:zeroOrMore>
51+
</rng:element>
52+
</rng:define>
53+
54+
<rng:start>
55+
<rng:choice>
56+
<rng:ref name="gallery" />
57+
</rng:choice>
58+
</rng:start>
59+
</rng:grammar>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { visitXML } from "@web/core/utils/xml";
2+
3+
export class galleryArchParser {
4+
parse(xmlDoc) {
5+
const imageField = xmlDoc.getAttribute("image_field");
6+
const limit = xmlDoc.getAttribute("limit") || 80;
7+
const fieldsForTooltip = [];
8+
let tooltipTemplate = undefined;
9+
visitXML(xmlDoc, (node) => {
10+
if (node.tagName === "field") {
11+
fieldsForTooltip.push(node.getAttribute("name"));
12+
}
13+
if (node.tagName === "tooltip-template") {
14+
tooltipTemplate = node;
15+
}
16+
})
17+
return {
18+
imageField,
19+
limit,
20+
fieldsForTooltip,
21+
tooltipTemplate,
22+
}
23+
}
24+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/** @odoo-module */
2+
3+
import { Component, onWillStart, onWillUpdateProps, useState } from "@odoo/owl";
4+
import { useService } from "@web/core/utils/hooks";
5+
import { standardViewProps } from "@web/views/standard_view_props";
6+
import { Layout } from "@web/search/layout";
7+
import { usePager } from "@web/search/pager_hook";
8+
9+
export class GalleryController extends Component {
10+
static template = "awesome_gallery.GalleryController";
11+
12+
static props = {
13+
...standardViewProps,
14+
archInfo: Object,
15+
Model: Function,
16+
Renderer: Function,
17+
};
18+
19+
static components = { Layout }
20+
21+
setup() {
22+
this.orm = useService("orm");
23+
this.model = useState(
24+
new this.props.Model(
25+
this.orm,
26+
this.props.resModel,
27+
this.props.archInfo,
28+
this.props.fields,
29+
)
30+
);
31+
32+
onWillStart(async () => {
33+
await this.model.loadImages(this.props.domain);
34+
});
35+
36+
onWillUpdateProps(async (nextProps) => {
37+
if (JSON.stringify(nextProps.domain) !== JSON.stringify(this.props.domain)) {
38+
await this.model.loadImages(nextProps.domain);
39+
}
40+
});
41+
42+
usePager(() => {
43+
const gallery = this.model.pager
44+
return {
45+
offset: gallery.offset,
46+
limit: gallery.limit,
47+
total: this.model.recordsLength,
48+
onUpdate: async ({ offset, limit }) => {
49+
gallery.offset = offset;
50+
gallery.limit = limit;
51+
await this.model.loadImages(this.props.domain);
52+
},
53+
};
54+
});
55+
}
56+
57+
async onImageUpload(record_id, image_binary) {
58+
this.model.uploadImage(record_id, image_binary, this.props.domain);
59+
}
60+
61+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<templates xml:space="preserve">
3+
4+
<t t-name="awesome_gallery.GalleryController">
5+
6+
<Layout display="props.display">
7+
8+
<t t-component="props.Renderer" model="this.model" onImageUpload.bind="onImageUpload" tooltipTemplate="props.archInfo.tooltipTemplate"/>
9+
10+
</Layout>
11+
12+
</t>
13+
14+
</templates>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/** @odoo-module */
2+
3+
import { Component, useState } from "@odoo/owl";
4+
import { GalleryModel } from "./gallery_model";
5+
import { url } from "@web/core/utils/urls";
6+
import { useService } from "@web/core/utils/hooks";
7+
import { FileUploader } from "@web/views/fields/file_handler";
8+
import { useTooltip } from "@web/core/tooltip/tooltip_hook";
9+
10+
export class GalleryImage extends Component {
11+
static template = "awesome_gallery.GalleryImage";
12+
static props = {
13+
record: Object,
14+
model: GalleryModel,
15+
onImageUpload: Function,
16+
tooltipTemplate: {
17+
optional: true,
18+
type: String,
19+
},
20+
};
21+
static components = { FileUploader }
22+
23+
setup() {
24+
this.action = useState(useService("action"));
25+
26+
if (this.props.tooltipTemplate) {
27+
useTooltip("tooltip", {
28+
info: { record: this.props.record },
29+
template: this.props.tooltipTemplate,
30+
});
31+
}
32+
}
33+
34+
switchToFormView(resId) {
35+
this.action.switchView("form", { resId });
36+
}
37+
38+
get imageUrl() {
39+
return url("/web/image", {
40+
model: this.props.model.resModel,
41+
id: this.props.record.id,
42+
field: this.props.model.imageField,
43+
unique: this.props.record.write_date,
44+
});
45+
}
46+
47+
async _onFileUploaded({ data }) {
48+
await this.props.onImageUpload(this.props.record.id, data);
49+
}
50+
51+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<templates xml:space="preserve">
3+
4+
<t t-name="awesome_gallery.GalleryImage">
5+
6+
<div t-ref="tooltip" class="card w-100 mb-4" t-on-click="() => this.switchToFormView(props.record.id)" t-att-data-tooltip="this.props.record[this.props.model.tooltipField]">
7+
8+
<img t-if='props.record[props.model.imageField]' loading="lazy" t-att-src="imageUrl"/>
9+
10+
<div t-on-click.stop="" class="h-100 opacity-0 opacity-100-hover">
11+
<FileUploader onUploaded.bind="_onFileUploaded">
12+
13+
<t t-set-slot="toggler">
14+
<span class="btn btn-link btn-primary p-0 me-3">Upload image</span>
15+
</t>
16+
17+
</FileUploader>
18+
</div>
19+
20+
</div>
21+
22+
23+
24+
</t>
25+
26+
</templates>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/** @odoo-module */
2+
3+
import { KeepLast } from "@web/core/utils/concurrency";
4+
import { url } from "@web/core/utils/urls";
5+
6+
export class GalleryModel {
7+
constructor(orm, resModel, archInfo, fields) {
8+
this.orm = orm;
9+
this.resModel = resModel;
10+
const { imageField, limit, fieldsForTooltip } = archInfo;
11+
this.imageField = imageField;
12+
this.fieldsForTooltip = fieldsForTooltip;
13+
this.limit = limit;
14+
this.keepLast = new KeepLast();
15+
this.fields = fields;
16+
this.pager = { offset: 0, limit: limit };
17+
}
18+
19+
async loadImages(domain) {
20+
const specification = {
21+
[this.imageField]: {},
22+
write_date: {},
23+
}
24+
for (const field of this.fieldsForTooltip) {
25+
specification[field] = {};
26+
}
27+
const { length, records } = await this.keepLast.add(
28+
this.orm.webSearchRead(this.resModel, domain, {
29+
limit: this.pager.limit,
30+
offset: this.pager.offset,
31+
specification,
32+
context: {
33+
bin_size: true,
34+
}
35+
})
36+
);
37+
this.records = records;
38+
this.recordsLength = length;
39+
40+
}
41+
42+
async uploadImage(record_id, image_binary, domain) {
43+
await this.orm.webSave(
44+
this.resModel,
45+
[record_id],
46+
{
47+
[this.imageField]: image_binary,
48+
},
49+
{
50+
specification: {},
51+
}
52+
)
53+
await this.loadImages(domain);
54+
}
55+
}

0 commit comments

Comments
 (0)