Skip to content

Commit

Permalink
[IMP] estate: wizard,controllers,security
Browse files Browse the repository at this point in the history
*Add 'Properties' menu to list available properties with images and
pagination on website  using of controller.

- Created a 'Properties' menu on the website to display available properties in
 a grid.
- Added image support for properties and displayed them on the front end.
- Implemented pagination to display a maximum of 6 properties per page.
- Created a dedicated, responsive property detail page using Bootstrap.

*Add 'Add Offer' wizard to Real Estate module for bulk offer creation

- Added 'Add Offer' button to the estate property tree view header.
- Implemented wizard for selecting multiple properties to make an offer.
- Wizard includes fields for price, validity, and buyer details.
- Added 'Make an Offer' and 'Cancel' buttons to handle business logic and
cancellation.

*Implement security restrictions for Real Estate module

- Created security.xml file to define security groups and access rights.
- Added Real Estate Agent and Manager groups with appropriate access levels.
- Set access rights: full access for Managers, limited access for Agents, and r
estricted access for non-agents.
- Defined record rules to restrict access to properties based on the agent
assigned.
- Implemented bypass for invoice creation security checks in estate_account
 module using `sudo()`.
- Added company-based record rules for multi-company security management.
- Restricted Settings menu visibility to Managers only.
  • Loading branch information
krku-odoo committed Aug 29, 2024
1 parent d811ef0 commit 3c09613
Show file tree
Hide file tree
Showing 16 changed files with 313 additions and 12 deletions.
2 changes: 2 additions & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import models
from . import controller
from . import wizard
9 changes: 6 additions & 3 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@
"name": "Real Estate",
"version": "1.0",
"license": "LGPL-3",
"category": "Real Estate",
"category": "Real Estate/Brokerage",
"summary": "Manage properties",
"depends": ["base_setup", "mail"],
"depends": ["base", "mail"],
"data": [
"security/security.xml",
"security/ir.model.access.csv",
"report/estate_property_reports.xml",
"report/estate_property_templates.xml",
"report/estate_property_sub_templates.xml",
"security/ir.model.access.csv",
"views/real_estate_property.xml",
"wizard/estate_property_offer.xml",
"views/estate_property_tree_views.xml",
"views/estate_property_offer_view.xml",
"views/estate_property_templates.xml",
"views/property_type_views.xml",
"views/property_tag.xml",
"views/res_users_view.xml",
Expand Down
1 change: 1 addition & 0 deletions estate/controller/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property_controller
50 changes: 50 additions & 0 deletions estate/controller/estate_property_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from odoo import http
from odoo.http import request


class EstatePropertyController(http.Controller):

@http.route(["/properties"], type="http", auth="public", website=True)
def properties_list(self, page=1, **kwargs):
try:
page = int(page)
except ValueError:
page = 1
Property = request.env["estate.property"].sudo()
properties_per_page = 6

total_properties = Property.search_count(
[
("state", "in", ["offer received", "offer accepted"]),
]
)
total_pages = (
total_properties + properties_per_page - 1
) // properties_per_page
page = max(1, min(page, total_pages))
offset = (page - 1) * properties_per_page
properties = Property.search(
[
("state", "in", ["offer received", "offer accepted"]),
],
limit=properties_per_page,
offset=offset,
)
return request.render(
"estate.property_template",
{
"properties": properties,
"page": page,
"total_pages": total_pages,
},
)

@http.route("/properties/<int:record_id>", type="http", auth="public", website=True)
def get_record(self, record_id, **kwargs):
record = request.env["estate.property"].sudo().browse(record_id)
if not record.exists():
return request.not_found()
return request.render(
"estate.property_view_details",
{"record": record},
)
Empty file removed estate/data/master_data.xml
Empty file.
2 changes: 1 addition & 1 deletion estate/demo/estate_demo.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<odoo>
<data>
<data noupdate="1">
<record id="estate_property_demo" model="estate.property">
<field name="name">Big Villa</field>
<field name="state">new</field>
Expand Down
9 changes: 8 additions & 1 deletion estate/models/estate_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class MyModel(models.Model):
salesman_id = fields.Many2one(
"res.users",
string="Salesman",
default=lambda self: self.env.user.partner_id,
default=lambda self: self.env.user,
)
buyer_id = fields.Many2one(
"res.partner",
Expand All @@ -58,6 +58,13 @@ class MyModel(models.Model):
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
total_area = fields.Float(compute="_compute_total_area")
best_offer = fields.Float(compute="_compute_best_offer")
property_image = fields.Image("property image")
company_id = fields.Many2one(
"res.company",
string="Company",
required=True,
default=lambda self: self.env.company,
)

_sql_constraints = [
("property_name", "unique(name)", "The property name must be unique!"),
Expand Down
17 changes: 13 additions & 4 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_estate_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1
estate.access_real_estate_property_type,access_real_estate_property_type,estate.model_real_estate_property_type,base.group_user,1,1,1,1
estate.access_real_estate_property_tag,access_real_estate_property_tag,estate.model_real_estate_property_tag,base.group_user,1,1,1,1
estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,0,0,0
estate.access_estate_property_type,access_estate_property_type,estate.model_real_estate_property_type,base.group_user,1,0,0,0
estate.access_estate_property_tag,access_estate_property_tag,estate.model_real_estate_property_tag,base.group_user,1,0,0,0
estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,0,0,0
estate.access_add_offer,access_add_offer,estate.model_property_offer_wizard,base.group_user,1,0,0,0
access_estate_property_group_user,access_estate_property_group_user,model_estate_property,estate.estate_group_user,1,1,1,0
estate.access_estate_property_type_group_user,access_estate_property_type_group_user,estate.model_real_estate_property_type,estate.estate_group_user,1,0,0,0
estate.access_estate_property_tag_group_user,access_estate_property_tag_group_user,estate.model_real_estate_property_tag,estate.estate_group_user,1,0,0,0
access_estate_property_manager,access_estate_property_manager,model_estate_property,estate.estate_group_manager,1,1,1,0
estate.access_estate_property_type_manager,access_estate_property_type_manager,estate.model_real_estate_property_type,estate.estate_group_manager,1,1,1,1
estate.access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_real_estate_property_tag,estate.estate_group_manager,1,1,1,1
estate.access_estate_property_offer_manager,access_estate_property_offer_manager,estate.model_estate_property_offer,estate.estate_group_manager,1,1,1,1
estate.access_add_offer_manager,access_add_offer_manager,estate.model_property_offer_wizard,estate.estate_group_manager,1,1,1,1
32 changes: 32 additions & 0 deletions estate/security/security.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<odoo>
<record id="estate_group_user" model="res.groups">
<field name="name">Agent</field>
<field name="category_id" ref="base.module_category_real_estate_brokerage"/>
</record>
<record id="estate_group_manager" model="res.groups">
<field name="name">Manager</field>
<field name="category_id" ref="base.module_category_real_estate_brokerage"/>
<field name="implied_ids" eval="[(4, ref('estate_group_user'))]"/>
</record>
<record id="rule_estate_group_user" model="ir.rule">
<field ref="model_estate_property" name="model_id"/>
<field name="name">Agent permissions</field>
<field name="perm_unlink" eval="False"/>
<field name="groups" eval="[Command.link(ref('estate.estate_group_user'))]"/>
<field name="domain_force">['|', ('salesman_id', '=', user.id), ('salesman_id', '=', False)]</field>
</record>

<record id="rule_estate_group_manager" model="ir.rule">
<field ref="model_estate_property" name="model_id"/>
<field name="name">Manager permissions</field>
<field name="perm_unlink" eval="False"/>
<field name="groups" eval="[Command.link(ref('estate.estate_group_manager'))]"/>
</record>

<record model="ir.rule" id="estate_property_rule_estate_group_user">
<field name="name">multi-company security</field>
<field name="model_id" ref="model_estate_property"/>
<field name="global" eval="True"/>
<field name="domain_force">['|',('company_id', '=', False),('company_id', 'in', company_ids)]</field>
</record>
</odoo>
137 changes: 137 additions & 0 deletions estate/views/estate_property_templates.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<odoo>
<template id="property_template" name="Property List">
<t t-call="website.layout">
<div class="container my-5">
<h1 class="text-center mb-4">Available Properties</h1>
<div class="row">
<t t-foreach="properties" t-as="property">
<div class="col-lg-4 col-md-6 mb-4 d-flex align-items-stretch">
<div class="card shadow-sm h-100">
<img t-if="property.property_image" t-att-src="image_data_uri(property.property_image)" class="card-img-top" alt="Property Image" style="height: 300px; width: 400px; object-fit: cover;"/>
<div class="card-body d-flex flex-column">
<h5 class="card-title">
<t t-esc="property.name"/>
</h5>
<div class="mt-auto">
<a t-att-href="'/properties/%s' % property.id" class="btn btn-primary btn-block">View Details</a>
</div>
</div>
</div>
</div>
</t>
</div>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<li class="page-item" t-att-class="page == 1 and 'disabled'">
<a t-att-href="'/properties?page=%d' % (page - 1)" class="page-link" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
<t t-foreach="range(1, total_pages + 1)" t-as="p">
<li class="page-item" t-att-class="p == page and 'active'">
<a t-att-href="'/properties?page=%d' % p" class="page-link">
<t t-esc="p"/>
</a>
</li>
</t>
<li class="page-item" t-att-class="page == total_pages and 'disabled'">
<a t-att-href="'/properties?page=%d' % (page + 1)" class="page-link" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</div>
</t>
</template>




<template id="property_view_details" name="View Property">
<t t-call="website.layout">
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card shadow-lg">
<div class="row g-0">
<div class="col-md-7">
<img t-if="record.property_image" t-att-src="image_data_uri(record.property_image)" class="img-fluid h-100 w-100 rounded-start" alt="Property image"/>
</div>
<div class="col-md-5">
<div class="card-body">
<h3 class="card-title">
<t t-esc="record.name"/>
</h3>
<hr/>
<p class="card-text">
<strong>State:</strong>
<t t-esc="record.state"/>
</p>
<p class="card-text">
<strong>Expected Price:</strong>
<t t-esc="record.expected_price" />
</p>
<p class="card-text">
<strong>Selling Price:</strong>
<t t-esc="record.selling_price" />
</p>
<hr/>
<p class="card-text">
<small class="text-muted">
<div>
<strong>Type:</strong>
<t t-esc="record.property_type_id.name"/>
</div>
<div>
<strong>Salesman:</strong>
<t t-esc="record.salesman_id.name"/>
</div>
<div>
<strong>Postcode:</strong>
<t t-esc="record.postcode"/>
</div>
<div>
<strong>Available From:</strong>
<t t-esc="record.date_availability"/>
</div>
<div>
<strong>Bedrooms:</strong>
<t t-esc="record.bedrooms"/>
</div>
<div>
<strong>Living Area (sqm):</strong>
<t t-esc="record.living_area"/>
</div>
<div>
<strong>Facades:</strong>
<t t-esc="record.facades"/>
</div>
<div>
<strong>Garage:</strong>
<t t-esc="record.garage" t-if="record.garage"/>
<t t-if="not record.garage">No</t>
</div>
<div>
<strong>Garden:</strong>
<t t-esc="record.garden" t-if="record.garden"/>
<t t-if="not record.garden">No</t>
</div>
<div>
<strong>Description:</strong>
<t t-esc="record.description" t-if="record.garden"/>
</div>
</small>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</t>
</template>


</odoo>
6 changes: 5 additions & 1 deletion estate/views/estate_property_tree_views.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<field name="model">estate.property</field>
<field name="arch" type="xml">
<tree string="Estate Properties" sample="1" decoration-bf="state=='offer accepted'" decoration-success="state=='offer received' or state=='offer accepted'" decoration-muted="state=='sold'">
<header>
<button name="%(estate.action_add_offer_wizard)d" type="action" string="Add Offer" class="oe_highlight"/>
</header>
<field name="name" />
<field name="postcode" />
<field name="date_availability" optional="hidden" />
Expand Down Expand Up @@ -41,7 +44,8 @@
<group>
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" />
<group>
<!-- <field name="state"/> -->
<field name="company_id"/>
<field name="property_image" widget="image"/>
<field name="property_type_id" options="{'no_create': true}" />
<field name="postcode" />
<field name="date_availability" />
Expand Down
2 changes: 1 addition & 1 deletion estate/views/real_estate_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<menuitem id="test_first_level_menu" name="Advertisement">
<menuitem id="test_model_menu_action" action="estate.real_estate_action" />
</menuitem>
<menuitem id="test_property_type_menu" name="Settings">
<menuitem id="test_property_type_menu" name="Settings" groups="estate.estate_group_manager">
<menuitem id="property_type_action" action="estate_property_type_action" name="Property Type" />
<menuitem id="property_tag_action" action="real_estate_property_tag_action" name="Property Tag" />
</menuitem>
Expand Down
1 change: 1 addition & 0 deletions estate/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property_offer
24 changes: 24 additions & 0 deletions estate/wizard/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from odoo import fields, models


class AddOffer(models.TransientModel):
_name = "property.offer.wizard"
_description = "Add offer"
_order = "price desc"

price = fields.Float(string="Price", required=True)
partner_id = fields.Many2one("res.partner", string="Partner", required=True)
validity = fields.Integer(string="Validity (days)", default=7)

def action_make_offer(self):
context = self.env.context
active_ids = context.get("active_ids", [])
for property_id in active_ids:
self.env["estate.property.offer"].create(
{
"price": self.price,
"validity": self.validity,
"partner_id": self.partner_id.id,
"property_id": property_id,
}
)
29 changes: 29 additions & 0 deletions estate/wizard/estate_property_offer.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<odoo>
<record id="property_offer_wizard_form" model="ir.ui.view">
<field name="name">estate.property.offer.form.wizard</field>
<field name="model">property.offer.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="price" />
<field name="partner_id" />
<field name="validity" />
</group>
</sheet>
<footer>
<button name="action_make_offer" type="object" string="Make an Offer" class="btn-primary"/>
<button special="cancel" string="Cancel" class="btn-secondary"/>
</footer>
</form>
</field>
</record>

<record id="action_add_offer_wizard" model="ir.actions.act_window">
<field name="name">Offer</field>
<field name="res_model">property.offer.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="property_offer_wizard_form"/>
<field name="target">new</field>
</record>
</odoo>
Loading

0 comments on commit 3c09613

Please sign in to comment.