Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ahta - Server framework 101 #209

Open
wants to merge 21 commits into
base: 18.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a946f85
[IMP] estate: Finish Chapter 1: Architecture Overview, Chapter 2: A N…
ataha1 Dec 16, 2024
b926817
[IMP] estate: Finish Chapter 4: Security - A Brief Introduction
ataha1 Dec 16, 2024
b83d00c
[IMP] estate: Chapter 5: Finally, Some UI To Play With
ataha1 Dec 17, 2024
6a57c68
[IMP] estate: use linter to format files
ataha1 Dec 17, 2024
b33e023
[IMP] estate: add list and form views
ataha1 Dec 17, 2024
c043cf5
[IMP] estate: add search view
ataha1 Dec 18, 2024
627f7df
[IMP] estate: add the real estate property type table
ataha1 Dec 18, 2024
835ea34
[IMP] estate: Add the Real Estate Property Tag table.
ataha1 Dec 18, 2024
31110d2
[IMP] estate: Add the Real Estate Property Offer table.
ataha1 Dec 18, 2024
7f92485
[IMP] estate: Use Computed Fields And Onchanges
ataha1 Dec 19, 2024
a981754
[IMP] estate: Use Actions To Cancel and set a property as sold
ataha1 Dec 19, 2024
ebc6946
[IMP] estate: Resolve Warnings & Issues
ataha1 Dec 20, 2024
a7af1bb
[IMP] estate: Add Constraints
ataha1 Dec 20, 2024
b78a6d2
[IMP] estate: Add Inline View, Widget and List Order
ataha1 Dec 20, 2024
63f1ad1
[IMP] estate: Add The Sprinkles
ataha1 Dec 23, 2024
19a5ba9
[IMP] estate: Add Inheritance to Real Estate Module
ataha1 Dec 24, 2024
124e92e
[IMP] estate: Interact With Invoicing App
ataha1 Dec 26, 2024
3a20c9f
[IMP] estate: Add Kaban View
ataha1 Dec 26, 2024
956d380
[IMP] awesome_owl: Finish Chapter 1 Sections 1-5
ataha1 Dec 26, 2024
cce23d4
[IMP] awesome_owl: Finish Chapter 1 Sections 6-9
ataha1 Dec 27, 2024
01b39f1
[IMP] awesome_owl: Finish Chapter 1 Sections 10-11
ataha1 Dec 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/

# vscode settings
.vscode/
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cSpell.words": ["odoo"]
ataha1 marked this conversation as resolved.
Show resolved Hide resolved
}
9 changes: 9 additions & 0 deletions awesome_owl/static/src/card/card.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from "@odoo/owl";

export class Card extends Component {
static template = "awesome_owl.card";
static props = {
title: { type: String },
content: { type: String },
};
}
15 changes: 15 additions & 0 deletions awesome_owl/static/src/card/card.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.card">
<div class="card d-inline-block m-2" style="width: 18rem;">
<div class="card-body">
<h5 class="card-title"><t t-out="props.title"/></h5>
<p class="card-text">
<t t-out="props.content"/>
</p>
</div>
</div>
</t>

</templates>
16 changes: 16 additions & 0 deletions awesome_owl/static/src/counter/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component, useState } from "@odoo/owl";

export class Counter extends Component {
static template = "awesome_owl.counter";
static props = { onChange: { type: Function, optional: true } };
setup() {
this.state = useState({ value: 0 });
}

increment() {
this.state.value++;
if (this.props.onChange) {
this.props.onChange();
Comment on lines +12 to +13

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: this syntax works also

Suggested change
if (this.props.onChange) {
this.props.onChange();
this.props.onChange?.();

}
}
}
13 changes: 13 additions & 0 deletions awesome_owl/static/src/counter/counter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_owl.counter">
<div class="card d-inline-block m-2">
<div class="card-body">
<p>Counter: <t t-esc="state.value"/></p>
<button class="btn btn-primary" t-on-click="increment">Increment</button>
</div>
</div>
</t>

</templates>
21 changes: 18 additions & 3 deletions awesome_owl/static/src/playground.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
/** @odoo-module **/

import { Component } from "@odoo/owl";
import { Component, markup, useState } from "@odoo/owl";
import { Counter } from "./counter/counter";
import { Card } from "./card/card";
import { TodoList } from "./todolist/todolist";

export class Playground extends Component {
static template = "awesome_owl.playground";
static template = "awesome_owl.playground";
static components = { Counter, Card, TodoList };
static props = {};

value = markup("<div class='text-primary'>some text</div>");

setup() {
this.state = useState({ sum: 0 });
// this.incrementSum = this.incrementSum.bind(this);
}

incrementSum() {
this.state.sum++;
}
}
12 changes: 10 additions & 2 deletions awesome_owl/static/src/playground.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
<templates xml:space="preserve">

<t t-name="awesome_owl.playground">
<div class="p-3">
hello world
<div class="card d-inline-block m-2" >
<Counter onChange.bind="incrementSum"/>
<Counter onChange.bind="incrementSum"/>
<p class="m-2">
Total sum:
<t t-esc="state.sum"/>
</p>
</div>
<Card title="'card 1'" content="'content of card 1'" />
<Card title="'card 2'" content="value"/>
<TodoList/>
</t>

</templates>
24 changes: 24 additions & 0 deletions awesome_owl/static/src/todolist/todo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, onMounted, useRef } from "@odoo/owl";

export class Todo extends Component {
static template = "awesome_owl.todo";
static props = {
todo: {
id: Number,
description: String,
isCompleted: Boolean,
},
toggleState: Function,
};
setup() {
this.stateRef = useRef("todo_item_ref");
onMounted(() => {
if (this.props.todo.isCompleted) {
this.stateRef.el.checked = true;
}
});
}
toggleState() {
this.props.toggleState(this.props.todo.id);
}
}
11 changes: 11 additions & 0 deletions awesome_owl/static/src/todolist/todo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.todo">

<input type="checkbox" t-on-change="toggleState" t-ref="todo_item_ref"/>
<li t-att-class="{'text-decoration-line-through text-muted':props.todo.isCompleted}">
<t t-esc="props.todo.description"/>
</li>

</t>
</templates>
44 changes: 44 additions & 0 deletions awesome_owl/static/src/todolist/todolist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component, useState, useRef, onMounted } from "@odoo/owl";
import { Todo } from "./todo";

export class TodoList extends Component {
static template = "awesome_owl.todolist";
static props = {};
static components = { Todo };

setup() {
this.state = useState({
todos: [
{ id: 0, description: "buy milk", isCompleted: false },
{ id: 1, description: "drink water", isCompleted: false },
{ id: 2, description: "play soccer", isCompleted: true },
],
});
this.todoInputRef = useRef("todo_input");
onMounted(() => {
this.todoInputRef.el.focus();
});
}

addTodo(event) {
if (event.keyCode == 13 && event.target.value != "") {
this.state.todos.push({
id: this.state.todos.length,
description: event.target.value,
isCompleted: false,
});
event.target.value = "";
console.log(this.state.todos);
}
}

toggleState(id) {
const index = this.state.todos.findIndex((todo) => todo.id == id);
if (index != -1) {
this.state.todos[index] = {
...this.state.todos[index],
isCompleted: !this.state.todos[index].isCompleted,
};
}
}
}
15 changes: 15 additions & 0 deletions awesome_owl/static/src/todolist/todolist.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_owl.todolist">

<div>
<input type="text" placeholder="Enter a new task" t-on-keyup="addTodo" t-ref="todo_input"/>
<ol>
<t t-foreach="state.todos" t-as="todo" t-key="todo.id">
<Todo todo="todo" toggleState.bind="toggleState"/>
</t>
</ol>
</div>

</t>
</templates>
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
19 changes: 19 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Real Estate",
"author": "Odoo",
"website": "https://www.odoo.com/page/realestate",
"version": "0.1",
"application": True,
"installable": True,
"depends": ["base"],
"data": [
"security/ir.model.access.csv",
"views/estate_property_type_views.xml",
"views/estate_property_tag_views.xml",
"views/estate_property_offer_views.xml",
"views/estate_property_views.xml",
"views/estate_menus.xml",
"views/estate_res_user_views.xml",
],
"license": "LGPL-3",
}
5 changes: 5 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import estate_res_user
128 changes: 128 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_is_zero, float_compare


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Estate Property"
_order = "id desc"

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False,
default=lambda x: fields.Datetime.add(fields.Datetime.today(), months=3),
)
expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
)
active = fields.Boolean(default=True)
state = fields.Selection(
string="State",
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("canceled", "Canceled"),
],
default="new",
copy=False,
)
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
salesperson_id = fields.Many2one(
"res.users", string="Salesperson", default=lambda self: self.env.user
)
tags_ids = fields.Many2many("estate.property.tag", string="Tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
total_area = fields.Float(compute="_compute_total_area")
best_price = fields.Float(compute="_compute_best_price")

_sql_constraints = [
(
"check_positive_expected_price",
"CHECK(expected_price>0)",
"The expected price should be positive.",
),
(
"check_positive_selling_price",
"CHECK(selling_price>0)",
"The selling price should be positive.",
),
]

@api.depends("garden_area", "living_area")
def _compute_total_area(self):
for record in self:
record.total_area = record.garden_area + record.living_area

@api.depends("offer_ids.price")
def _compute_best_price(self):
for record in self:
prices = record.offer_ids.mapped("price")
record.best_price = max(prices, default=0)

@api.onchange("garden")
def _onchange_garden(self):
if self.garden:
self.garden_area = 10
self.garden_orientation = "north"
else:
self.garden_area = 0
self.garden_orientation = False

def action_sold(self):
print("I am in property")
self.ensure_one()
if self.state != "canceled":
self.state = "sold"
else:
raise UserError("Canceled properties can't be sold")
return True

def action_cancel(self):
for record in self:
if record.state != "sold":
record.state = "canceled"
else:
raise UserError("Sold properties can't be sold")
return True

@api.constrains("selling_price", "expected_price")
def _check_selling(self):
for record in self:
if (
not float_is_zero(record.selling_price, precision_digits=3)
and float_compare(
record.selling_price,
record.expected_price * 0.9,
precision_digits=3,
)
< 0
):
raise UserError(
"The selling price cannot be lower than 90% of the expected price"
)

@api.ondelete(at_uninstall=False)
def _unlink_if_not_new_or_cancelled(self):
for record in self:
if record.state not in ("new", "canceled"):
raise UserError("Only new or canceled properties can be deleted")
Loading