Full ViewDas odoo Admin Buch für die cBUZZ OFFICE Suite

Vorwort

Das OpenERP System wurde ursprünglich unter dem Namen TinyERP entwickelt. Inzwischen wird von uns aktuell die Version 8.0 unter dem Namen odoo Bausteine in der cBUZZ OFFICE Suite - verwendet.

Das odoo Admin Buch für die cBUZZ OFFICE Suite kommt in der Creative Commons Lizenz CC-BY-NA-SA zum Leser und hilft über konkrete „Use Cases“ dem Systemadministrator bei der Abbildung des Kunden-Workflow in der cBUZZ OFFICE Suite. Im Folgenden werden beispielhaft Lösungen bei der Konfiguration und Administration der odoo Bausteine in der cBUZZ OFFICE Suite gezeigt.

Sie finden die Bücher von cBUZZ PRESS auch auf der Seite http://books.IFO.net

Das System als Admin steuern

Anlegen einer neuen leeren Datenbank

Der Zusammenhang zwischen Datenbank und Domainname

  • Auf der Seite https://c000.4myPAL.com/web/database/manager#action=database_manager können Sie eine Datenbank zB. mit dem Namen test anlegen. Dafür benötigen Sie einen Benutzer mit root Rechten für die Postgres Datenbank! Sie erfinden hier auch das Passwort für den admin Benutzer mit allen Rechten, innerhalb Ihrer neuen test Instanz.


  • Mit der Eingabe von https://test.IhreDomain.at wird Ihnen die Website der soeben installierten Instanz mit der Datenbank test angezeigt. Das Passwort für den Benutzer admin haben Sie selbst bei der Datenbankanlage festgelegt! Melden Sie sich jetzt als admin an.



TIPP

Als Admin müssen Sie natürlich die Domain test.IhreDomain.at im Nameserver auf die IP Adresse Ihres cBUZZ SERVERs zeigen lassen und dem Apache Server zB. im Virtualmin ihres cBUZZ SERVERs mitteilen, wie er auf http://test.IhreDomain.at reagieren soll! Natürlich können Sie diese Einstellungen auch bei der IFO.net Technik in Auftrag geben.

Die FIBU ist immer die erste APP, die wir installieren

  • Es ist wichtig, dass wir in einer neuen Datenbank gleich zu Beginn die App Buchhaltung und Finanzen (account_accountant) installieren, weil dadurch andere Apps von start weg den richtigen Kontorahmen verwenden.

im Fenster Konfiguration Buchhaltung  wählen Sie jetzt Österreichische Buchführung. Die Perioden werden Sie auf die Meldung Ihre Umsatzsteuer (monatlich oder vierteljährlich) abstimmen. Eine Umstellung der Perioden ist auch später immer wieder möglich.


Danach antworten Sie im Fenster Einstellung der Buchhaltung und wählen zB. einen vierstelligen Kontorahmen für Ihr Unternehmen !!!


Gratulation

Sie haben soeben als Adminstrator die stabile Grundlage für einen neuen Mandanten für Ihre odoo APPs in der cBUZZ OFFICE Suite  gelegt.


Die Buchführung gibt es bereits für über einhundert Länder:


Weiter Systemeinstellungen

  • Als nächstes kontrollieren Sie rechts oben unter Administrator > Einstellungen  im Feld Sprache die Eingabe von German / Deutsch und im Feld Zeitzone die Eingabe von Europe/Vienna.
  • Wechseln Sie jetzt links oben von Website auf Einstellungen. Unter Einstellungen > Module > Lokale Apps können Sie jetzt vorinstallierte App Odoo Live Support (im_odoo_support ) deinstallieren. Dieser englischsprachige kostenpflichtige Support bringt Ihnen auf Ihrem eigenen odoo Server keinen Nutzen. Klicken Sie dazu auf die Überschrift Odoo Live Support und dann auf den Button Deinstallieren.
  • Zum Aufwärmen installieren Sie jetzt die APP Website Builder und die APP IBAN Bankkonten. Sie finden die Apps unter Einstellungen > Module > Lokale Apps oder Odoo Apps. Weitere Apps finden sie, wenn Sie rechts oben die Auswahl Odoo Apps x oder  Featured x  weg klicken.
  • Legen Sie jetzt über Einstellungen > Konfiguration > Website im Feld Website Name den Titel für Ihre Websites an.
  • Aktivieren Sie im Feld Sprachen zB.  Englisch und German/Deutsch. Wählen Sie im Feld Standardsprache German/Deutsch. Klicken Sie im oberen Bereich auf Übernehmen, damit die Änderungen auch abgespeichert werden.
  • Über Einstellungen > Konfiguration > Grundeinstellungen klicken Sie jetzt auf Konfigurieren Sie Ihre Unternehmensdaten. Dabei können Sie beliebig oft zwischen Bearbeiten und Speichern wechseln.
  • Sie können das Bankkonto auch später für die Apps im Bereich Rechnungserstellung und Verbuchung anlegen: Im Feld Bankkonten können Sie für Ihre Einnahmen auch schon jetzt das dafür vorgesehene Konto eintragen. Klicken Sie auf Eintrag hinzufügen. Weil wir vorhin die IBAN App installiert haben, können Sie jetzt im Feld Bankkontotyp IBAN Konto auswählen. Im Feld Konto-Inhaber klicken Sie auf Anlegen und Bearbeiten. Klicken Sie neben Bezeichnung auf das Feld (X Ist ein Unternehmen?) Klicken Sie auf Speichern. Wenn Sie die so verbunden Daten zum Kontoinhaber verändern möchten klicken Sie auf den Pfeil -> in derselben Zeile. Im Feld Bank können Sie genau so mit Anlegen und Bearbeiten eine neue Bank anlegen. Danach klicken Sie auf Speichern und nochmals auf Speichern.
  • Sie können jetzt in der oberen Zeile auf API Einstell... oder in der linken Spalte Grundeinstellungen klicken.

Tipp:

  • Beim Menüpunkt Einstellungen > Konfiguration > Grundeinstellungen sollten Sie in der Zeile Einstellungen den Eintrag Multi-Company Verwaltung niemals verwenden, wenn Sie in unseren Anleitungen dazu nicht aufgefordert werden. Das System ist so mächtig, dass Sie hier die Zusammenhänge vor der Verwendung zuerst genau studieren sollten.
  • Wie oben beschrieben, können Sie auch ohne der Funktion Multi-Company Verwaltung beliebig viele Firmen und Mandanten durch ein einfaches Anlegen einer neuen Datenbank umsetzten.

Gratulation:

Sie haben jetzt als Adminstrator eine neue Instanz für Ihre odoo APPs in der cBUZZ OFFICE Suite wie Website Builder, eComerce Online Shop, BLOG, Event Kalender oder Gemeinsame Notizen, Zusammenarbeit im Team, oder zur Rechnungserstellung, Buchhaltung, Lagerverwaltung, oder zur Verwaltung Ihrer Mitarbeiter, Lieferanten, Kunden und Offenen Posten erstellt. Die Installation dieser APPs funktioniert einfach über den Menüpunkt Einstellungen > Module > Odoo Apps.

Konfiguration von installierten APPs

Dokument Management System

Konfiguration der Verzeichnisse

Das cBUZZ OFFICE Dokument Management System verbindet virtuelle Ordner mit Dokumenten. Der virtuelle Ordner kann verwendet werden, um Anhänge eines Dokumentes zu verwalten, herunterzuladen und zu drucken. Die Verzeichnisse werden automatisch entsprechend der installierten Module angelegt.

Rufen Sie die Konfigurationshilfe einfach unter Einstellungen > Konfiguration > Dokumente auf. Es wird automatisch die App document installiert,


Dieser Wizard konfiguriert Ihre Verzeichnisse automatisch entsprechend der installierten Module. Sie erreichen damit:

  • Eine Indizierung von Dokumenten
  • Eine Volltextsuche in Dateianhängen












eCommerce Online Shop

Information

  • App Name: eCommerce Online Shop - Verkaufen Sie Ihre Produkte Online
  • Website: https://www.odoo.com/page/e-commerce
  • Kategorie/Name: Website | website_sale
  • Erzeugtes Menü: Verkauf ‣ Konfiguration ‣ Produktkategorien & Attribute Berichte ‣ Öffentliche Produktkategorien

Erstellte Ansichten

  • INHERIT Add to Cart (qweb)
  • INHERIT Alternative Products (qweb)
  • INHERIT Continue Shopping Button (qweb)
  • INHERIT Discussion (qweb)
  • INHERIT Header Shop My Cart Link
  • INHERIT List View (qweb)
  • INHERIT List View of Variants (qweb)
  • INHERIT Product Attribute's Filters (qweb)
  • INHERIT Product Categories (qweb)
  • INHERIT Product Description (qweb)
  • INHERIT Product attributes (qweb)
  • INHERIT Reduction Code (qweb)
  • INHERIT Select Quantity (qweb)
  • INHERIT Shop (qweb)
  • INHERIT Shop Editor (qweb)
  • INHERIT Suggested Products in my cart (qweb)
  • INHERIT content_new_product (qweb)
  • INHERIT payment.transaction.form.website_sale (form)
  • INHERIT product.attribute.tree.type (tree)
  • INHERIT product.pricelist.website.form (form)
  • INHERIT product.template.product.website.form (form)
  • INHERIT sale.order.form.acquirer (form)
  • INHERIT variants.template.tree (tree)
  • INHERIT variants.template.tree (tree)
  • 404 (qweb)
  • Category list (qweb)
  • Product (qweb)
  • Product item (qweb)
  • Products (qweb)
  • Search hidden fields (qweb)
  • Shopping Cart (qweb)
  • checkout (qweb)
  • confirmation (qweb)
  • payment (qweb)
  • product.public.category.form (form)
  • product.public.category.tree (tree)
  • product_price (qweb)
  • total (qweb)
  • variants (qweb)

Abhängigkeiten

 Bezeichnung 
 Status 
 website Installed
 sale Installed
 payment Installed 






Website über den HTML Editor und über das Menü Einstellungen steuern

Kann man den Zugang zum E-Commerce-Menü beschränken?

Wenn Sie für eine B2B-Plattform das Shop-Menü und die E-Commerce-Inhalte (Produktseiten, etc.) für alle anonymen Website Besucher verstecken möchten, erreichen Sie das über zwei Einstellungen.

  • Wählen Sie Einstellungen ‣ Technisch ‣ Sicherheit ‣ Rechte für Daten und erstellen Sie eine neue Regel, die alle Menüseiten ausblendet, die mit /shop beginnen.:
    • Bezeichnung: website-menu
    • Objekt: Website Menü
    • Zugriffsrechte: Setzte Leseberechtigung
    • Regeldefinition:  [('url','not like','%/shop%')]
    • Gruppen, Gruppenname hinzufügen: Öffentlich


  • So können Sie Shop Seiten für Besucher verstecken, die nicht als Angestellte oder Portal-Benutzer registriert sind.
    • Wählen Sie auf Ihre Shopseite Anpassen ‣ HTML-Editor
    • Wählen Sie links vom Button Save den Menüeintrag Products
    • Ändern Sie das tag Attribut am beginn der Seite von
      <t name="Products" t-name="website_sale.products">
      auf
      <t name="Products" t-name="website_sale.products" groups="base.group_user,base.group_portal">


  • Alternativ können Sie den Eintrag auch über Einstellungen ‣ Technisch ‣ Benutzerinterface ‣ Ansichten eintragen. suchen Sie dafür nach dem Eintrag Products mit der externen ID website_sale.products


Jetzt können Sie Ihre Website als anonyme Person aufrufen. Sie werden das Shop-Menü nicht mehr sehen und wenn Sie die Seite http://IhreDomain.at/shop aufrufen, werden Sie eine leere Seite erhalten.

Eine Website wieder löschen


Siehe auch: https://github.com/odoo/odoo/issues/440

Die neue QWeb Template Engine im QWeb Report Generator

QWeb REPORT Design-Grundlagen

Die odoo Bausteine in der cBUZZ OFFICE Suite unterstützen einheitliche Berichte, indem sie auf eine allgemeinen Layout-Struktur aufbauen, die die einzelnen Zeilen in zwölf Spalten unterteilen.


I

Jedes Blatt hat einen oberen, einen unteren, einen linken und einen rechten Rand in Millimetern, die in Einstellungen ‣ Technisch ‣ Berichte ‣ Papierformat festgelegt werden.

II 

Die odoo Module in der cBUZZ OFFICE Suite unterteilen ein Blatt in Reihen (CSS-Klasse row, also "Zeile") mit einem oberen Rand (CSS-Klasse mt, also "MarginTop") und einem unteren Rand (CSS-Klasse mb, also "MarginBottom"). Möchten Sie eine Zeile mit 8px oberen Rand und 32px unteren Rand einstellen, können Sie das mit folgendem Code definieren:

<div class="row mt8 mb32">Ihre neue Zeile!</div>


Mögliche Ränder sind: mt0, mt4, mt8, mt16, mt32, mt48, mt64, mt92, mt128, mb0, mb4, mb8, mb16, mb32, mb48, mb64, mb92, mb128

III 

Jede Zeile wird in zwölf Spalten unterteilt. Möchten Sie eine bestimmte Position und Breite eines div Containers angeben? Ihnen stehen dafür zwei CSS-Klassennamen zu Verfügung:

  • Die Breite definieren Sie mit: col-[screensize]-[column-factor]
  • Die Position zu definieren Sie mit: col-[screensize]-[position-type]-[column-factor]

Möchten Sie einen zentrierten div Container mit einer Breite von 50% anlegen? Verwenden Sie einfach folgenden Eintrag:

<div class="col-xs-6 col-xs-offset-3">Großer zentrierter Container</div>

Erklärung

col-xs-6 bedeutet eine Breite von 6 Spalten. Da es insgesamt 12 Spalten gibt, ist das genau die Hälfte davon, also 50%.   

col-xs-offset-3 bedeutet, dass die Anzeige des Behälters nach einer Breite von 3 Spalten beginnt, es bleiben somit auch 3 Spalten bis zum rechten Rand. So ist der Behälter genau in der Mitte des Blattes.

Erhältliche Bildschirmgrößen

xs (Breite kleiner als 768px), sm (mindest-Breite: 768px), md (mindest-Breite: 992px), lg (mindest-Breite: 1200px)

Verfügbare Spaltenfaktoren
col-xs-offset- 1,2,3,4,5,6,7,8,9,10,11,12

Erhältliche Positionstypen

  • offset (absolute horizontale Position in einer Zeile),
  • pull (relative Position, links vom letzten div Element)
  • push (relative Position, rechts vom letzten div Element)

Beispiel: Möchten Sie einen kleinen Behälter (nur eine Spalten breit) auf der rechten Seite des Großen zentrierten Containers (siehe Beispiel oben) im Abstand von zwei Spalten positionieren? Verwenden Sie einfach folgenden Code:

<div class="col-xs-1 col-xs-push-2">Kleiner Container auf der rechten Seite</div>

Links mit weiteren Infos:

Anpassung der Rechnungen in der QWeb Report Engine

Zu diesem Thema können Sie gerne unter https://cbuzz.ifo.net/forum/entwicklung-9/question/wie-verandere-ich-meine-rechnungen-in-der-odoo-qweb-report-engine-9 Fragen stellen.

Anpassung der Nummernkreise

Den Rechnungs-Nummernkreis legen wir fest unter Einstellungen ‣ Konfiguration ‣ Finanzen unter der Überschrift E-Rechnungen & Zahlungen im Feld Nächste Rechnungsnummer.

 Default Beispiel Ergebnis
 VK/%(year)s/  
 AR/%(y)s  
 AR/0001 
 GSV/%(year)s/ 
 GS-AR/%(y)s 
 GS-AR/0001   
 EK/%(year)s/ ER/%(y)s  
 ER/0001   
 GSL/%(year)s/ GS-ER/%(y)s GS-ER/0001   

Weiter Informationen dazu findet man im Buch odoo Bausteine in der cBUZZ OFFICE Suite unter Anpassung der Nummernkreise.

Benutzerdefinierte Fußzeile und Firmenlogo

Die Benutzerdefinierte Fußzeile, das Firmenlogo und die (alte RML) Konfiguration finden wir unter:

  1. Einstellungen ‣ Konfiguration ‣ Finanzen
  2. Klicke unter der Überschrift Bank & Kasse auf den Button Konfigurieren Sie Ihre Hausbanken.
  3. Gehe zum TAB Reportkonfiguration. Schalte Benutzerdefinierte Fußzeile ein und gehe in das Feld Berichtsfuß.
  4. Bearbeite die Ansicht nach Bedarf.
  5. Mit Vorschau Kopf-/Fußzeile bekommen wir einen ersten Eindruck von den Anpassungen. Die PDF Datei wird hier noch über die alte RML Technik erstellt.

Der Standard Seitenrand im A4 Papierformat

  1. Einstellungen ‣ Technisch ‣ Berichte ‣ Papierformat
  2. Rufe European A4 auf
  3. Passe das Format an.


Die Kundenadresszeile im Hauptbereich der der neuen Qweb Technik ändern

Rufe Einstellungen ‣ Technisch‣ Berichte ‣ Berichte auf und suche nach Rechnungen und rufe den Eintrag auf. Wähle Suche nach verknüpften QWeb Ansichten. So findet man report_invoice und report_invoice_document für Anpassungen.

Damit die Kundenadresszeile für ein Fensterkuvert links ausgedruckt wird, können wir in report_invoice_document folgende Änderungen erstellen.

<div class="row">
  <div class="col-xs-6 col-xs-offset-0"> <!-- div class="col-xs-5 col-xs-offset-7" -->                 <address t-field="o.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "address","zip"], "no_marker": true}' />
 <!-- address t-field="o.partner_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: true}"/ -->
  <span t-field="o.partner_id.vat"/>
</div>

Die Kopf- und Fußzeile in der neuen Qweb Technik

Um die Kopf- und Fußzeile für Angebot, Auftragsbestätigung, Rechnung, usw. in der neuen Qweb Technik bearbeiten zu können rufe folgendes auf:

  1. Einstellungen ‣ Technisch ‣ Benutzer-Interface ‣ Ansichten
  2. Suche nach external_layout oder external in der QWeb Auswahl
  3. Man sieht: external_layout, external_layout_header und external_layout_footer.
  4. Bearbeite diese Ansichten nach Bedarf und passe damit den PDF-Ausdruck an.


Die Datei external_layout_header könnte dann so angepasst werden:

<?xml version="1.0"?>
<t t-name="report.external_layout_header">
    <div class="header">
        <div class="row">      <!-- div class="col-xs-3" -->
            <div class="col-xs-12 col-xs-offset-0 text-right"> 
               <img width="220" src="https://ifo.ifo.net/website/image/ir.attachment/270_17ecfd3/datas" alt="IFO.net" /><br/>
               <img width="600" height="8" src="https://ifo.ifo.net/website/image/ir.attachment/275_5b334fc/datas" alt="Linie" />
               <br/>Regensburg - WIEN - Graz - KIRCHBACH .::. <strong>IFO.net Internet Service GmbH</strong><br/>
                <!-- img t-if="company.logo" t-att-src="'data:image/png;base64,%s' % company.logo" style="max-height: 45px;"/ -->
            </div>
            <!-- div class="col-xs-9 text-right" style="margin-top:20px;" t-field="company.rml_header1"/ -->
        </div>

        <!-- div class="row zero_min_height">
            <div class="col-xs-12">
                <div style="border-bottom: 1px solid black;"/>
            </div>
        </div -->

        <div class="row">          <!-- div class="col-xs-3" -->
            <div class="col-xs-6 col-xs-offset-6 text-right">
               Nr 79 - imPULSzentrum.eu<br/>
               8082 Kirchbach in Steiermark / AUSTRIA<br/><br/>
               Tel: +43.3116/2021-0 Fax: 2021-22<br/>
               eMail: service@IFO.net <strong>http://IFO.net</strong>

                <!-- div t-field="company.partner_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: true}" style="border-bottom: 1px solid black;"/ -->
            </div>
        </div>
    </div>
</t> 


Die Datei external_layout_footer könnte danach so angepasst werden:

<?xml version="1.0"?>
<t t-name="report.external_layout_footer">
    <div class="footer">
   <div class="col-xs-12 col-xs-offset-0 text-right">
    cBUZZ steht für CORPORATE BUSINESS für Firmenkunden .::. www.cBUZZ.eu
    <img width="762" height="8" src="https://ifo.ifo.net/website/image/ir.attachment/275_5b334fc/datas" alt="Linie" />
    <span style="font-size:11px">
    <br/>Es gelten die allgemeinen Geschäftsbedingungen der IFO.net Internet Service GmbH, die Sie unter http://agb.IFO.net finden. Jeder Garantieanspruch<br/>
            beschränkt sich auf die Behebung des Fehlers. Für die Datensicherung ist der Kunde selbst verantwortlich. Für Folgeschäden kann keine Haftung<br/>
            übernommen werden. Gerichtsstand Graz. Bankkonto Ihre Bank IBAN: AT99 2222 0000 0012 2550 BIC: CAWWAT2G170
    </span>

    <span style="font-size:10px">
    <div class="text-center" style="border-top: 0px solid black;">
    <ul class="list-inline">
        <li/>
        <li>
          Seite: <span class="page"/>
        </li>
        <li>
          von: <span class="topage"/>
          <ul class="list-inline js_language_selector mt16">
	</ul>
        </li>
      </ul>
    </div>
    </span>

        <!-- div class="text-center" style="border-top: 1px solid black;">
            <ul t-if="not company.custom_footer" class="list-inline">
               ............
            </ul>
        </div -->

    </div>      
    </div>
</t>

Anpassung der Mahnungen in der QWeb Report Engine

Die Kundenadresszeile der Mahnungen ändern

Rufe Einstellungen ‣ Technisch‣ Berichte ‣ Berichte auf und suche nach Mahnung und rufe den Eintrag Statistik Mahnungen (account_followup.report_followup) auf. Klicke auf Suche nach verknüpften QWeb Ansichten. So findet man report_followup für Anpassungen.

Damit die Kundenadresszeile für ein Fensterkuvert links ausgedruckt wird, können wir in report_followup folgende Änderungen erstellen.

<div class="page">
  <div class="row">
    <div class="col-xs-5 col-xs-offset-0">
                <span style="font-size:11px"><br/><br/><br/>Absender: IFO.net GmbH, 8082 Kirchbach Nr. 79 / AUSTRIA<br/></span>
                <div class="text-center" style="border-top: 1px solid black;" />
                
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.name"/>
                <span t-if="o.partner_id.street2"><br/>&#160;&#160;&#160;&#160;&#160;<span
 t-field="o.partner_id.street2"/></span>
                
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.street"/>               
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.zip"/>&#160;<span t-field="o.partner_id.city"/>              
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.country_id"/><br/><br/>  
<!-- address t-field="o.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "address", "zip"], "no_marker": true}' /--> <!-- address t-field="o.partner_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: true}"/ -->
</div> </div>

Die Mahnungstexte anpassen

Rufe Finanzen Konfiguration ‣ Verschiedenes ‣ E-Mail-vorlagen auf und suche nach follow-up oder Mahnung.

A bit urging second payment follow-up reminder email

Betreff ${user.company_id.name} Zahlungserinnerung
Dear ${object.name},
We are disappointed to see that despite sending a reminder, that your account is now seriously overdue. It is essential that immediate payment is made, otherwise we will have to consider placing a stop on your account which means that we will no longer be able to supply your company with (goods/services). Please, take appropriate measures in order to carry out this payment in the next 8 days. If there is a problem with paying invoice that we are not aware of, do not hesitate to contact our accounting department. so that we can resolve the matter quickly. Details of due payments is printed below.
Best Regards,
${user.name}
${object.get_followup_table_html() | safe}

Deutsch

Betreff Mahnung für ${object.name} von ${user.company_id.name}
Sehr geehrte Kundin, sehr geehrter Kunde,
wir sind enttäuscht, dass Ihr Konto trotz Mahnung überfällig ist.
Wichtig ist jetzt, dass Sie für eine sofortige Zahlung sorgen, damit eine kontinuierliche Nutzung unserer Dienstleistungen für Sie gewährleistet bleibt.
Bitte zahlen Sie OFFENE Beträge umgehend auf unser Raiffeisen Konto IBAN: AT19 3817 0000 0000 2550 | BIC: RZSTAT2G170 ein, oder füllen Sie auf der Seite http://konto.IFO.net das Formular aus, für eine Bezahlung im SEPA Lastschriftverfahren über Ihr IBAN Konto.
Ihr Kontakt: business@IFO.net oder +43.664.4559677. Wenn Sie Fragen haben, zögern Sie bitte nicht uns zu kontaktieren.
Besten Dank für Ihr Interesse an unseren Produkten und Dienstleistungen.
IFO.net Internet Service GmbH
${object.get_followup_table_html() | safe}

Default payment follow-up reminder e-mail / First polite payment follow-up reminder email

Betreff ${user.company_id.name} Zahlungserinnerung
Dear ${object.name},
Exception made if there was a mistake of ours, it seems that the following amount stays unpaid. Please, take appropriate measures in order to carry out this payment in the next 8 days. Would your payment have been carried out after this mail was sent, please ignore this message. Do not hesitate to contact our accounting department.
Best Regards,
${user.name}
${object.get_followup_table_html() | safe}

Deutsch

Betreff Mahnung für ${object.name} von ${user.company_id.name}
Sehr geehrte Kundin, sehr geehrter Kunde,
bitte kontrollieren Sie folgenden Kontoauszug und überweisen Sie umgehend OFFENE Beträge auf unser Raiffeisen Konto IBAN: AT19 3817 0000 0000 2550 | BIC: RZSTAT2G170.
TIPP: Sichern Sie sich die kontinuierliche Nutzung unserer Dienstleistungen und füllen Sie einmalig das Lastschriftformular unter http://konto.IFO.net aus. Das ist bequem und sicher! Sie brauchen die Rechnungen der IFO.net GmbH dann nicht mehr einzahlen. Der Betrag wird jeweils im SEPA Lastschriftverfahren Ihrem IBAN Konto zur Bezahlung vorgelegt.
Ihr Kontakt: business@IFO.net oder +43.664.4559677. Wenn Sie Fragen haben, zögern Sie bitte nicht uns zu kontaktieren.
Besten Dank für Ihr Interesse an unseren Produkten und Dienstleistungen.
IFO.net Internet Service GmbH
${object.get_followup_table_html() | safe}

Urging payment follow-up reminder email

Betreff ${user.company_id.name} Zahlungserinnerung
Dear ${object.name},
Despite several reminders, your account is still not settled. Unless full payment is made in next 8 days, legal action for the recovery of the debt will be taken without further notice. I trust that this action will prove unnecessary and details of due payments is printed below. In case of any queries concerning this matter, do not hesitate to contact our accounting department.
Best Regards,
${user.name}
${object.get_followup_table_html() | safe}

Deutsch

Betreff Mahnung für ${object.name} von ${user.company_id.name}
Sehr geehrte Kundin, sehr geehrter Kunde,
trotz mehrerer Mahnungen ist Ihr Konto noch immer nicht beglichen!
Nur durch eine sofortige Zahlung können Sie eine Sperre unserer Dienstleistungen und eine Klage der OFFENEN Beträge noch verhindern, die sonst ohne weitere Warnung erfolgen wird.
Bitte zahlen Sie die OFFENE Beträge sofort auf unser Raiffeisen Konto IBAN: AT19 3817 0000 0000 2550 | BIC: RZSTAT2G170 ein, oder füllen Sie auf der Seite http://konto.IFO.net das Formular aus, für eine Bezahlung im SEPA Lastschriftverfahren über Ihr IBAN Konto.
Bitte ersparen Sie sich die erheblichen Mehrkosten, die durch die weiteren technischen und rechtlichen Schritte bereits in den nächsten Tagen entstehen werden.
Ihr Kontakt: business@IFO.net oder +43.664.4559677. Wenn Sie Fragen haben, zögern Sie bitte nicht uns zu kontaktieren.
Besten Dank für Ihr Interesse an unseren Produkten und Dienstleistungen.
IFO.net Internet Service GmbH
${object.get_followup_table_html() | safe} 

Anpassung der Angebote in der QWeb Report Engine

Die Kundenadresszeile der Angebote ändern

Rufe Einstellungen ‣ Technisch‣ Berichte ‣ Berichte auf und suche nach Angebot oder order und rufe das Modell sale.order (Angebot / Bestellung) auf. Klicke auf Suche nach verknüpften QWeb Ansichten. So findet man report_sale_order_document für Anpassungen.

Damit die Kundenadresszeile für ein Fensterkuvert links ausgedruckt wird, können wir in report_sale_order_document das Original:

            <div class="row">
                <div class="col-xs-6">
                    <strong t-if="o.partner_shipping_id == o.partner_invoice_id">Invoice and shipping address:</strong>
                    <strong t-if="o.partner_shipping_id != o.partner_invoice_id">Invoice address:</strong>
                    <div t-field="o.partner_invoice_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;, &quot;phone&quot;, &quot;fax&quot;], &quot;no_marker&quot;: true}"/>
                    <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
                    <div t-if="o.partner_shipping_id != o.partner_invoice_id" class="mt8">
                        <strong>Shipping address:</strong>
                        <div t-field="o.partner_shipping_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;, &quot;phone&quot;, &quot;fax&quot;], &quot;no_marker&quot;: true}"/>
                        <p t-if="o.partner_id.vat">VAT: <span t-field="o.partner_id.vat"/></p>
                    </div>                        
                </div>
                <div class="col-xs-5 col-xs-offset-1">
                    <div t-field="o.partner_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: true}"/>
                </div>
            </div>

ändern in:

  <div class="row">
    <div class="col-xs-5 col-xs-offset-0">
                <span style="font-size:11px"><br/><br/><br/>Absender: IFO.net GmbH, 8082 Kirchbach Nr. 79 / AUSTRIA<br/></span>
                <div class="text-center" style="border-top: 1px solid black;" />
                
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.name"/>
                <span t-if="o.partner_id.street2"><br/>&#160;&#160;&#160;&#160;&#160;<span
 t-field="o.partner_id.street2"/></span>
                
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.street"/>               
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.zip"/>&#160;<span t-field="o.partner_id.city"/>              
<br/>&#160;&#160;&#160;&#160;&#160;<span t-field="o.partner_id.country_id"/><br/><br/>  
<!-- address t-field="o.partner_id" t-field-options='{"widget": "contact", "fields": ["name", "address", "zip"], "no_marker": true}' /--> <!-- address t-field="o.partner_id" t-field-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: true}"/ -->
</div> </div>

Im obigen Beispiel verzichten wir auf die Spalte Lieferadresse, weil wir ohnehin nur Dienstleistungen verkaufen! 

Die Angebotstexte im eMail anpassen

Rufe Finanzen Konfiguration ‣ Verschiedenes ‣ E-Mail-vorlagen auf und suche nach sale oder Angebot.

Anpassen der Rechnung via eMail

Anpassen des Versendens einer Rechnung via HTML eMail

Rufe Finanzen ‣ Verschiedenes ‣ E-Mail-Vorlagen auf und suche nach Invoice oder Rechnung.

Rufe die Vorlage auf und klicke links oben auf Bearbeiten. In der Zeile mit den Icons kann man mit Show Source auch direkt den HTML Quelltext bearbeiten.

<div style="font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 12px; color: rgb(34, 34, 34); background-color: #FFF; ">
 <p>An: ${object.partner_id.name}</p><p>Sehr geehrter Kunde, <br></p><p>besten Dank für Ihr Interesse an unseren Produkten und Dienstleistungen.</p><p>Für Sie wurde eine neue Rechnung erstellt, die Sie im Anhang erhalten:<br></p>
    
<p style="border-left: 1px solid #8e0000; margin-left: 30px;">
       &nbsp;&nbsp;<strong>REFERENZEN</strong><span id="result_box" class="short_text" lang="de"><span class="hps alt-edited"></span></span><br>
       &nbsp; Rechnungs Nummer: <strong>${object.number}</strong><br>
       &nbsp; Rechnungs Betrag: <strong>${object.amount_total} ${object.currency_id.name}</strong><br>
       &nbsp; Rechnungs Datum: ${object.date_invoice}<br>
       % if object.origin:
       &nbsp;&nbsp;Order reference: ${object.origin}<br>
       % endif
       % if object.user_id:
       &nbsp; Ihr Kontakt: <span style="font-weight: bold;"><a href="mailto:business@IFO.net">business@IFO.net</a></span> oder +43.664.4559677
       % endif
    </p>
    <p><br></p><p>Wenn Sie Fragen haben, zögern Sie bitte nicht uns zu kontaktieren.</p>
    <p>Danke für Ihre Wahl.</p><p> ${object.company_id.name or 'us'}</p>
    <br>
    <br>
    <div style="width: 375px; margin: 0px; padding: 0px; background-color: #8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; background-repeat: repeat no-repeat;">
        <h3 style="margin: 0px; padding: 2px 14px; font-size: 12px; color: #DDD;">
            <strong style="text-transform:uppercase;">${object.company_id.name}</strong></h3>
    </div>
    <div style="width: 347px; margin: 0px; padding: 5px 14px; line-height: 16px; background-color: #F2F2F2;">
        <span style="color: #222; margin-bottom: 5px; display: block; ">
        % if object.company_id.street:
            ${object.company_id.street}<br>
        % endif
        % if object.company_id.street2:
            ${object.company_id.street2}<br>
        % endif
        % if object.company_id.city or object.company_id.zip:
            ${object.company_id.zip} ${object.company_id.city}<br>
        % endif
        % if object.company_id.country_id:
            ${object.company_id.state_id and ('%s, ' %
object.company_id.state_id.name) or ''}
${object.company_id.country_id.name or ''}<br>
        % endif
        </span>
        % if object.company_id.phone:
            <div style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; ">
                Phone:&nbsp; ${object.company_id.phone}
            </div>
        % endif
        % if object.company_id.website:
            <div>
                Web: <span style="font-weight: bold;"><a href="https://cBUZZ.IFO.net">https://cBUZZ.IFO.net</a></span>
            </div>
        %endif
        
    </div>
</div>

Odoo für Entwickler

Aufbau einer Website in odoo

Das Tutorialhttps://www.odoo.com/documentation/8.0/howtos/website.html erklärt den Aufbau einer Website ab oddo V8 Schrit für Schritt

Mit Hilfe dieses Beitrags kann ein Programmierer den Quellcode zum oddo System rasch lesen und für eigene Projekte anpassen.

Model View Controller

Der Aufbau des odoo Systems läst sich als MVC (Model View Controller) Prinzip verstehen. Wobei die Darstellung der Daten vom Zugriff und der Speicherung getrennt ist.

Die Darstellung erfolgt im odoo Client (Web Browser, Firefox, Google Chrome) und wird mit Hilfe von serverseitig abgelegten XML-Dateien festgelegt. Die Modelle liegen in Tabellen der PostgreSQL Datenbank. Die Schnittstelle zwischen dem Modell und der Ansicht ist ein Python-Controller-Objekt.


Die odoo APPs in der cBUZZ OFFICE Suite definieren in der Datei /etc/odoo/openerp-server.conf den addons_path


Unter /usr/lib/python2.7/dist-packages/openerp/addons erstellen wir das Verzeichnis: my-academy mit folgender Struktur:

├── my-academy
│ ├── __init__.py # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein
│ ├── __openerp__.py # Enthält verschieden Paket Informationen
│   ├── security/       # 
│ │ ├── ir.model.access.csv
│ ├── controllers.py  # Das Modul mit ihren Objektdefinitionen
│ ├── static/ # Das static Verzeichnis wird angelegt, bleibt aber noch leer

│ ├── views/  # Definieren Sie hier ihre Ansichten (Aktionen, Wizards, Menüs, Masken)
│ │ ├── demo.xml

│   │   ├── templates.xml

mkdir /usr/lib/python2.7/dist-packages/openerp/addons/my-academy
chmod -R 755 /usr/lib/python2.7/dist-packages/openerp/addons/my-academy

./my-academy/__init__.py   # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein:

import controllers    # Einbinden des Moduls, das Hallo Welt :-) ausgeben wird.

./my-academy/__openerp__.py   # oder __oddo__.py steuert die APP Installation inkl. Abhängigkeiten mit Paketbeschreibung.

{
    'name': 'My Academy',
    'version': '1.0',
    'category': 'Tools',
    'summary': 'Courses, Sessions, Subscriptions',
    'description': """
Courses, Sessions, Subscriptions
================================

This module manages the definition of course, the organization,
sessions, and the subscriptions of participants.
""",
    'depends' : ['web'],
    'data' : [
        'views/demo.xml',
        'views/templates.xml',
    ],
    'images': [],
    'demo': [],
    'application': True,
    'installable' : True,
}

./my-academy/security/ir.model.access.csv     # Steuert Zugangsrechte (brauchen wir erst später im Beispiel)

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_academy,academy.academy,model_academy_academy,,1,0,0,0

./my-academy/controllers.py     # Das Modul mit ihrer Objektdefinition wird Hallo Welt :-) ausgeben

# -*- coding: utf-8 -*-
from openerp import http

class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public')
    def index(self, **kw):
        return "Hallo Welt :-)"

#     @http.route('/academy/academy/objects/', auth='public')
#     def list(self, **kw):

./my-academy/static/

  • Wir legen ein leeres static Verzeichnis an.


./my-academy/views/demo.xml     # Definiert die Datenfelder (brauchen wir erst später im Beispiel)

<?xml version="1.0" encoding="UTF-8"?>
<openerp>
    <data>
        <!--  -->
        <!--   <record id="object0" model="academy.academy"> -->
        <!--     <field name="name">Object 0</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object1" model="academy.academy"> -->
        <!--     <field name="name">Object 1</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object2" model="academy.academy"> -->
        <!--     <field name="name">Object 2</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object3" model="academy.academy"> -->
        <!--     <field name="name">Object 3</field> -->
        <!--   </record> -->
        <!--  -->
        <!--   <record id="object4" model="academy.academy"> -->
        <!--     <field name="name">Object 4</field> -->
        <!--   </record> -->
        <!--  -->
    </data>
</openerp>

./my-academy/views/templates.xml     # Definiert das QWeb Formular (brauchen wir erst später im Beispiel)

<?xml version="1.0" encoding="UTF-8"?>
<openerp>
    <data>
        <!-- <template id="listing"> -->
        <!--   <ul> -->
        <!--     <li t-foreach="objects" t-as="object"> -->
        <!--       <a t-attf-href="#{ root }/objects/#{ object.id }"> -->
        <!--         <t t-esc="object.display_name"/> -->
        <!--       </a> -->
        <!--     </li> -->
        <!--   </ul> -->
        <!-- </template> -->
        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->
        <!--     <t t-foreach="object._fields" t-as="field"> -->
        <!--       <dt><t t-esc="field"/></dt> -->
        <!--       <dd><t t-esc="object[field]"/></dd> -->
        <!--     </t> -->
        <!--   </dl> -->
        <!-- </template> -->
    </data>
</openerp>


Wenn Sie alle Dateien angelegt haben, starten Sie odoo neu:

/etc/init.d/odoo restart

Im odoo Back-End kann der Admin die lokale My Acadamy APP jetzt installieren.

Einstellungen ‣ Benutzer ‣ Benutzer

  • Event. den Benutzer auswählen und unter der Überschrift Bedienbarkeit das Feld Technische Eigenschaften aktivieren.
  • Danach unter Einstellungen ‣ ModuleUpdates Aktualisieren die neue APP bekannt machen.
  • Unter Einstellungen ‣ Module ‣ Lokale Apps bei My Academy auf Installieren klicken.

Als Entwickler können Sie auch den Entwicklermodus aktivieren. Klicken Sie rechts oben Ihr Benutzerkonto an und wählen Sie das Feld Über odoo. Im neuen Fenster klicken Sie auf Entwicklermodus wählen.

Aufruf im Internet Browser

Der einfache Controllers interpretiert den Browser Aufruf und sendet Daten zurück:

  • Die Seite https://Ihredomain.at/academy/academy/ wird jetzt Hallo Welt :-) ausgeben

Templates

Das generieren von HTML ist in purem Python war niemals sehr angenehm.

Die übliche Lösung sind ist Templates. Also Pseudo-Dokumente mit Platzhaltern und gedachter Anzeigelogik. Mit odoo können Sie jedes Python Template-System verwenden. Seit der Version odoo V8 steht Ihnen aber auch das odoo eigene QWeb Template-System zur Verfügung, das mit anderen odoo Funktionen interagiert.

Ergänzen Sie jetzt die beiden vorhin erstellten Dateien mit den im Quellcode fett gekennzeichneten Stellen:

./my-academy/controllers.py     # Das Modul mit ihrer Objektdefinition wird Hallo Welt :-) ausgeben

class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public')
    def index(self, **kw):
        return http.request.render('my-academy.index', {
            'teachers': ["Diana Padilla", "Jody Caroll", "Lester Vaughn"],
        })

#     @http.route('/academy/academy/objects/', auth='public')
#     def list(self, **kw):


./my-academy/views/templates.xml     # Definiert das QWeb Formular

<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data> <template id="index"> <title>Academy</title> <t t-foreach="teachers" t-as="teacher"> <p><t t-esc="teacher"/></p> </t> </template> <!-- <template id="object"> --> <!-- <h1><t t-esc="object.display_name"/></h1> --> <!-- <dl> -->

The templates iterates (t-foreach) on all the teachers (passed through the template context), and prints each teacher in its own paragraph.

Finally restart Odoo and update the module’s data (to install the template) by going to Settings ‣ Modules ‣ Local Modules ‣ Academy and clicking Upgrade.

Tipp: 

Alternativ können Sie odoo auch auf der Command-Line neu starten:

/etc/init.d/odoo restart
 
  • Die Seite https://Ihredomain.at/academy/academy/ wird jetzt folgendes ausgeben:


Storing data in Odoo

Odoo models map to database tables.

In the previous section we just displayed a list of string entered statically in the Python code. This doesn’t allow modifications and persistent storage thereof, so we’re now going to move our data to the database.

Defining the data model

Define a teacher model, and ensure it is imported from __init__.py so it is correctly loaded:

academy/models.py

from openerp import models, fields, api

class Teachers(models.Model):
    _name = 'academy.teachers'

    name = fields.Char()


Then setup basic access control for the model and add them to the manifest:

academy/__openerp__.py

    # always loaded
    'data': [
        'security/ir.model.access.csv',
        'templates.xml',
    ],
    # only loaded in demonstration mode


academy/security/ir.model.access.csv

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0


this simply gives read access (perm_read) to all users (group_id:id left empty).

Note

Data files (XML or CSV) have to be added to the module manifest, Python files (models or controllers) don’t but have to be imported from __init__.py (directly or indirectly)

Warning

the administrator user bypasses access control, he has access to all models even if not given access


Demonstration data

The second step is to add some demonstration data to the system so it’s possible to test it easily. This is done by adding a demo data file, which must be linked from the manifest:

academy/demo.xml

<openerp>
    <data>
        <record id="padilla" model="academy.teachers">
            <field name="name">Diana Padilla</field>
        </record>
        <record id="carroll" model="academy.teachers">
            <field name="name">Jody Carroll</field>
        </record>
        <record id="vaughn" model="academy.teachers">
            <field name="name">Lester Vaughn</field>
        </record>
    </data>
</openerp>

Tipp

Data files can be used for demo and non-demo data. Demo data are only loaded in “demonstration mode” and can be used for flow testing and demonstration, non-demo data are always loaded and used as initial system setup.

In this case we’re using demonstration data because an actual user of the system would want to input or import their own teachers list, this list is only useful for testing.

Accessing the data

The last step is to alter model and template to use our demonstration data:

  1. fetch the records from the database instead of having a static list
  2. Because search() returns a set of records matching the filter (“all records” here), alter the template to print each teacher’s name

academy/controllers.py

class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public')
    def index(self, **kw):
        Teachers = http.request.env['academy.teachers']
        return http.request.render('academy.index', {
            'teachers': Teachers.search([])
        })

#     @http.route('/academy/academy/objects/', auth='public')



academy/templates.xml

        <template id="index">
            <title>Academy</title>
            <t t-foreach="teachers" t-as="teacher">
                <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
            </t>
        </template>
        <!-- <template id="object"> -->


Restart the server and update the module (in order to update the manifest and templates and load the demo file) then navigate to http://localhost:8069/academy/academy/. The page should look little different: names should simply be prefixed by a number (the database identifier for the teacher).

Website support

Odoo bundles a module dedicated to building websites.

So far we’ve used controllers fairly directly, but Odoo 8 added deeper integration and a few other services (e.g. default styling, theming) via the website module.

  1. first, add website as a dependency to academy
  2. then add the website=True flag on the controller, this sets up a few new variables on the request object and allows using the website layout in our template
  3. use the website layout in the template

academy/__openerp__.py

    'version': '0.1',

    # any module necessary for this one to work correctly
    'depends': ['website'],

    # always loaded
    'data': [



academy/controllers.py

from openerp import http

class Academy(http.Controller):
    @http.route('/academy/academy/', auth='public', website=True)
    def index(self, **kw):
        Teachers = http.request.env['academy.teachers']
        return http.request.render('academy.index', {


academy/templates.xml

<openerp>
    <data>
        <template id="index">
            <t t-call="website.layout">
                <t t-set="title">Academy</t>
                <div class="oe_structure">
                    <div class="container">
                        <t t-foreach="teachers" t-as="teacher">
                            <p><t t-esc="teacher.id"/> <t t-esc="teacher.name"/></p>
                        </t>
                    </div>
                </div>
            </t>
        </template>
        <!-- <template id="object"> -->


After restarting the server while updating the module (in order to update the manifest and template) access http://localhost:8069/academy/academy/ should yield a nicer looking page with branding and a number of built-in page elements (top-level menu, footer, …)

The website layout also provides support for edition tools: click Sign In (in the top-right), fill the credentials in (admin / admin by default) then click Log In.

You’re now in Odoo “proper”: the administrative interface. For now click on the Website menu item (top-left corner.

We’re back in the website but as an administrator, with access to advanced edition features provided by the website support:

  • a template code editor (Customize ‣ HTML Editor) where you can see and edit all templates used for the current page
  • the Edit button in the top-left switches to “edition mode” where blocks (snippets) and rich text edition are available
  • a number of other features such as mobile preview or SEO


URLs and routing

Controller methods are associated with routes via the route() decorator which takes a routing string and a number of attributes to customise its behavior or security.

We’ve seen a “literal” routing string, which matches a URL section exactly, but routing strings can also use converter patterns which match bits of URLs and make those available as local variables. For instance we can create a new controller method which takes a bit of URL and prints it out:

academy/controllers.py

            'teachers': Teachers.search([])
        })

    @http.route('/academy/<name>/', auth='public', website=True)
    def teacher(self, name):
        return '<h1>{}</h1>'.format(name)


#     @http.route('/academy/academy/objects/', auth='public')
#     def list(self, **kw):
#         return http.request.render('academy.listing', {


restart Odoo, access http://localhost:8069/academy/Alice/ and http://localhost:8069/academy/Bob/ and see the difference.

As the name indicates, converter patterns don’t just do extraction, they also do validation and conversion, so we can change the new controller to only accept integers:

academy/controllers.py

            'teachers': Teachers.search([])
        })

    @http.route('/academy/<int:id>/', auth='public', website=True)
    def teacher(self, id):
        return '<h1>{} ({})</h1>'.format(id, type(id).__name__)


#     @http.route('/academy/academy/objects/', auth='public')


Restart Odoo, access http://localhost:8069/academy/2, note how the old value was a string, but the new one was converted to an integers. Try accessing http://localhost:8069/academy/Carol/ and note that the page was not found: since “Carol” is not an integer, the route was ignored and no route could be found.

Odoo provides an additional converter called model which provides records directly when given their id, let’s use that and create a generic page for teacher biographies:

academy/controllers.py

            'teachers': Teachers.search([])
        })

    @http.route('/academy/<model("academy.teachers"):teacher>/', auth='public', website=True)
    def teacher(self, teacher):
        return http.request.render('academy.biography', {
            'person': teacher
        })


#     @http.route('/academy/academy/objects/', auth='public')



academy/templates.xml

                </div>
            </t>
        </template>
        <template id="biography">
            <t t-call="website.layout">
                <t t-set="title">Academy</t>
                <div class="oe_structure"/>
                <div class="oe_structure">
                    <div class="container">
                        <p><t t-esc="person.id"/> <t t-esc="person.name"/></p>
                    </div>
                </div>
                <div class="oe_structure"/>
            </t>
        </template>
        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->


then change the list of model to link to our new controller:

academy/templates.xml

                <div class="oe_structure">
                    <div class="container">
                        <t t-foreach="teachers" t-as="teacher">
                            <p><a t-attf-href="/academy/{{ slug(teacher) }}">
                              <t t-esc="teacher.name"/></a>
                            </p>
                        </t>
                    </div>
                </div>



                <div class="oe_structure"/>
                <div class="oe_structure">
                    <div class="container">
                        <h3><t t-esc="person.name"/></h3>
                    </div>
                </div>
                <div class="oe_structure"/>


Restart Odoo and upgrade the module, then you can visit each teacher’s page. As an exercise, try adding blocks to a teacher’s page to write a biography, then go to another teacher’s page and so forth. You will discover, that your biography is shared between all teachers, because blocks are added to the template, and the biography template is shared between all teachers, when one page is edited they’re all edited at the same time.


Field edition

Data which is specific to a record should be saved on that record, so let us add a new biography field to our teachers:

academy/models.py

    _name = 'academy.teachers'

    name = fields.Char()
    biography = fields.Html()




academy/templates.xml

                <div class="oe_structure">
                    <div class="container">
                        <h3><t t-esc="person.name"/></h3>
                        <div><t t-esc="person.biography"/></div>
                    </div>
                </div>
                <div class="oe_structure"/>





Restart Odoo and update the views, reload the teacher’s page and… the field is invisible since it contains nothing.

For record fields, templates can use a special t-field directive which allows editing the field content from the website using field-specific interfaces. Change the person template to use t-field:

academy/templates.xml

                <div class="oe_structure"/>
                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <div t-field="person.biography"/>
                    </div>
                </div>
                <div class="oe_structure"/>



Restart Odoo and upgrade the module, there is now a placeholder under the teacher’s name and a new zone for blocks in Edit mode. Content dropped there is stored in the corresponding teacher’s biography field, and thus specific to that teacher.

The teacher’s name is also editable, and when saved the change is visible on the index page.

t-field can also take formatting options which depend on the exact field. For instance if we display the modification date for a teacher’s record:

academy/templates.xml

                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <p>Last modified: <i t-field="person.write_date"/></p>
                        <div t-field="person.biography"/>
                    </div>
                </div>



it is displayed in a very “computery” manner and hard to read, but we could ask for a human-readable version:

academy/templates.xml

                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <p>Last modified: <i t-field="person.write_date" t-field-options='{"format": "long"}'/></p>
                        <div t-field="person.biography"/>
                    </div>
                </div>


or a relative display:

academy/templates.xml

                <div class="oe_structure">
                    <div class="container">
                        <h3 t-field="person.name"/>
                        <p>Last modified: <i t-field="person.write_date" t-field-options='{"widget": "relative"}'/></p>
                        <div t-field="person.biography"/>
                    </div>
                </div>

Administration and ERP integration

A brief and incomplete introduction to the Odoo administration

The Odoo administration was briefly seen during the website support section. We can go back to it using Administrator ‣ Administrator in the menu (or Sign In if you’re signed out).

The conceptual structure of the Odoo backend is simple:

  1. first are menus, a tree (menus can have sub-menus) of records. Menus without children map to…
  2. actions. Actions have various types: links, reports, code which Odoo should execute or data display. Data display actions are called window actions, and tell Odoo to display a given model according to a set of views…
  3. a view has a type, a broad category to which it corresponds (a list, a graph, a calendar) and an architecture which customises the way the model is displayed inside the view.


Editing in the Odoo administration

By default, an Odoo model is essentially invisible to a user. To make it visible it must be available through an action, which itself needs to be reachable, generally through a menu.

Let’s create a menu for our model:

academy/__openerp__.py

    'data': [
        'security/ir.model.access.csv',
        'templates.xml',
        'views.xml',
    ],
    # only loaded in demonstration mode
    'demo': [




academy/views.xml

<openerp><data>
  <record id="action_academy_teachers" model="ir.actions.act_window">
    <field name="name">Academy teachers</field>
    <field name="res_model">academy.teachers</field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>
  <menuitem id="menu_academy_content_teachers"
            parent="menu_academy_content"
            action="action_academy_teachers"/>
</data></openerp>

then accessing http://localhost:8069/web/ in the top left should be a menu Academy, which is selected by default, as it is the first menu, and having opened a listing of teachers. From the listing it is possible to Create new teacher records, and to switch to the “form” by-record view.

If there is no definition of how to present records (a view) Odoo will automatically create a basic one on-the-fly. In our case it works for the “list” view for now (only displays the teacher’s name) but in the “form” view the HTML biography field is displayed side-by-side with the name field and not given enough space. Let’s define a custom form view to make viewing and editing teacher records a better experience:

academy/views.xml

    <field name="res_model">academy.teachers</field>
  </record>

  <record id="academy_teacher_form" model="ir.ui.view">
    <field name="name">Academy teachers: form</field>
    <field name="model">academy.teachers</field>
    <field name="arch" type="xml">
      <form>
        <sheet>
          <label for="name"/> <field name="name"/>
          <label for="biography"/>
          <field name="biography"/>
        </sheet>
      </form>
    </field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>


Relations between models

We have seen a pair of “basic” fields stored directly in the record. There are a number of basic fields. The second broad categories of fields are relational and used to link records to one another (within a model or across models).

For demonstration, let’s create a courses model. Each course should have a teacher field, linking to a single teacher record, but each teacher can teach many courses:

academy/models.py

    name = fields.Char()
    biography = fields.Html()

class Courses(models.Model):
    _name = 'academy.courses'

    name = fields.Char()
    teacher_id = fields.Many2one('academy.teachers', string="Teacher")



academy/security/ir.model.access.csv

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0
access_academy_courses,access_academy_courses,model_academy_courses,,1,0,0,0


let’s also add views so we can see and edit a course’s teacher:

academy/views.xml

    </field>
  </record>

  <record id="action_academy_courses" model="ir.actions.act_window">
    <field name="name">Academy courses</field>
    <field name="res_model">academy.courses</field>
  </record>
  <record id="academy_course_search" model="ir.ui.view">
    <field name="name">Academy courses: search</field>
    <field name="model">academy.courses</field>
    <field name="arch" type="xml">
      <search>
        <field name="name"/>
        <field name="teacher_id"/>
      </search>
    </field>
  </record>
  <record id="academy_course_list" model="ir.ui.view">
    <field name="name">Academy courses: list</field>
    <field name="model">academy.courses</field>
    <field name="arch" type="xml">
      <tree string="Courses">
        <field name="name"/>
        <field name="teacher_id"/>
      </tree>
    </field>
  </record>
  <record id="academy_course_form" model="ir.ui.view">
    <field name="name">Academy courses: form</field>
    <field name="model">academy.courses</field>
    <field name="arch" type="xml">
      <form>
        <sheet>
          <label for="name"/>
          <field name="name"/>
          <label for="teacher_id"/>
          <field name="teacher_id"/>
        </sheet>
      </form>
    </field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>
  <menuitem id="menu_academy_content_courses"
            parent="menu_academy_content"
            action="action_academy_courses"/>
  <menuitem id="menu_academy_content_teachers"
            parent="menu_academy_content"
            action="action_academy_teachers"/>


It should also be possible to create new courses directly from a teacher’s page, or to see all the courses a teacher gives, so add the inverse relationship to the teachers model:

academy/models.py

    name = fields.Char()
    biography = fields.Html()

    course_ids = fields.One2many('academy.courses', 'teacher_id', string="Courses")

class Courses(models.Model):
    _name = 'academy.courses'



academy/views.xml

      <form>
        <sheet>
          <label for="name"/> <field name="name"/>

          <label for="biography"/>
          <field name="biography"/>

          <field name="course_ids">
            <tree string="Courses" editable="bottom">
              <field name="name"/>
            </tree>
          </field>
        </sheet>
      </form>
    </field>


Discussions and notifications

Odoo provides technical models, which don’t fulfill business needs in and of themselves but add capabilities to business objects without having to build them by hand.

One of these is the Chatter system, part of Odoo’s email and messaging system, which can add notifications and discussion threads to any model. The model simply has to _inherit mail.thread, and add the message_ids field to its form view to display the discussion thread. Discussion threads are per-record.

For our academy, it makes sense to allow discussing courses to handle e.g. scheduling changes or discussions between teachers and assistants:

academy/models.py

class Courses(models.Model):
    _name = 'academy.courses'
    _inherit = 'mail.thread'

    name = fields.Char()
    teacher_id = fields.Many2one('academy.teachers', string="Teacher")



academy/views.xml

          <label for="teacher_id"/>
          <field name="teacher_id"/>
        </sheet>
        <div class="oe_chatter">
          <field name="message_follower_ids" widget="mail_followers"/>
          <field name="message_ids" widget="mail_thread"/>
        </div>
      </form>
    </field>
  </record>


At the bottom of each course form, there is now a discussion thread and the possibility for users of the system to leave messages and follow or unfollow discussions linked to specific courses.

Selling courses

Odoo also provides business models which allow using or opting in business needs more directly. For instance the website_sale module sets up an e-commerce site based on the products in the Odoo system. We can easily make course subscriptions sellable by making our courses specific kinds of products.

Rather than the previous classical inheritance, this means replacing our course model by the product model, and extending products in-place (to add anything we need to it).

First of all we need to add a dependency on website_sale so we get both products (via sale) and the ecommerce interface:

academy/__openerp__.py

    'version': '0.1',

    # any module necessary for this one to work correctly
    'depends': ['website_sale'],

    # always loaded
    'data': [


restart Odoo, update your module, there is now a Shop section in the website, listing a number of pre-filled (via demonstration data) products.

The second step is to replace the courses model by product.template, and add a new category of product for courses:

academy/__openerp__.py

        'security/ir.model.access.csv',
        'templates.xml',
        'views.xml',
        'data.xml',    
    ],
    # only loaded in demonstration mode
    'demo': [



academy/data.xml

<openerp><data>
  <record model="product.public.category" id="category_courses">
    <field name="name">Courses</field>
    <field name="parent_id" ref="website_sale.categ_others"/>
  </record>
</data></openerp>



academy/demo.xml

        <record id="vaughn" model="academy.teachers">
            <field name="name">Lester Vaughn</field>
        </record>

        <record id="course0" model="product.template">
            <field name="name">Course 0</field>
            <field name="teacher_id" ref="padilla"/>
            <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
            <field name="website_published">True</field>
            <field name="list_price" type="float">0</field>
            <field name="type">service</field>
        </record>
        <record id="course1" model="product.template">
            <field name="name">Course 1</field>
            <field name="teacher_id" ref="padilla"/>
            <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
            <field name="website_published">True</field>
            <field name="list_price" type="float">0</field>
            <field name="type">service</field>
        </record>
        <record id="course2" model="product.template">
            <field name="name">Course 2</field>
            <field name="teacher_id" ref="vaughn"/>
            <field name="public_categ_ids" eval="[(4, ref('academy.category_courses'), False)]"/>
            <field name="website_published">True</field>
            <field name="list_price" type="float">0</field>
            <field name="type">service</field>
        </record>
    </data>
</openerp>



academy/models.py

    name = fields.Char()
    biography = fields.Html()

    course_ids = fields.One2many('product.template', 'teacher_id', string="Courses")

class Courses(models.Model):
    _inherit = 'product.template'

    teacher_id = fields.Many2one('academy.teachers', string="Teacher")



academy/security/ir.model.access.csv

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_academy_teachers,access_academy_teachers,model_academy_teachers,,1,0,0,0



academy/views.xml

    </field>
  </record>

  <menuitem sequence="0" id="menu_academy" name="Academy"/>
  <menuitem id="menu_academy_content" parent="menu_academy"
            name="Academy Content"/>
  <menuitem id="menu_academy_content_teachers"
            parent="menu_academy_content"
            action="action_academy_teachers"/>


With this installed, a few courses are now available in the Shop, though they may have to be looked for.

Note

  • to extend a model in-place, it’s inherited without giving it a new _name
  • product.template already uses the discussions system, so we can remove it from our extension model
  • we’re creating our courses as published by default so they can be seen without having to log in


Altering existing views

So far, we have briefly seen:

  • the creation of new models
  • the creation of new views
  • the creation of new records
  • the alteration of existing models

We’re left with the alteration of existing records and the alteration of existing views. We’ll do both on the Shop pages.

View alteration is done by creating extension views, which are applied on top of the original view and alter it. These alteration views can be added or removed without modifying the original, making it easier to try things out and roll changes back.

Since our courses are free, there is no reason to display their price on the shop page, so we’re going to alter the view and hide the price if it’s 0. The first task is finding out which view displays the price, this can be done via Customize ‣ HTML Editor which lets us read the various templates involved in rendering a page. Going through a few of them, “Product item” looks a likely culprit.

Altering view architectures is done in 3 steps:

  1. Create a new view
  2. Extend the view to modify by setting the new view’s inherit_id to the modified view’s external id
  3. In the architecture, use the xpath tag to select and alter elements from the modified view

academy/templates.xml

                <div class="oe_structure"/>
            </t>
        </template>

        <template id="product_item_hide_no_price" inherit_id="website_sale.products_item">
            <xpath expr="//div[hasclass('product_price')]/b" position="attributes">
                <attribute name="t-if">product.price &gt; 0</attribute>
            </xpath>
        </template>

        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->

The second thing we will change is making the product categories sidebar visible by default: Customize ‣ Product Categories lets you toggle a tree of product categories (used to filter the main display) on and off.

This is done via the customize_show and active fields of extension templates: an extension template (such as the one we’ve just created) can be customize_show=True. This choice will display the view in the Customize menu with a check box, allowing administrators to activate or disable them (and easily customize their website pages).

We simply need to modify the Product Categories record and set its default to active=”True”:

academy/templates.xml

            </xpath>
        </template>

        <record id="website_sale.products_categories" model="ir.ui.view">
            <field name="active" eval="True"/>
        </record>

        <!-- <template id="object"> -->
        <!--   <h1><t t-esc="object.display_name"/></h1> -->
        <!--   <dl> -->

With this, the Product Categories sidebar will automatically be enabled when the Academy module is installed.

odoo APP Programmierung

PRAXIS: odoo APP Teil 1

(Quelle: mindissoftware.com und Teil2) Hier finden Sie ein Muster für die Programmierung eines Add-on-Moduls in odoo. Das Installationsskript kann leicht angepasst werden, um eine neue odoo APP aus GitHub zu installieren.

Das Beispiel hat den Namen Offene Akademie und wurde bei den odoo Open Days gezeigt. Der Quellcode von Raphael Collet wurde auch in GitHub veröffentlicht. Dieser Beitrag und das Beispielprogramm werden Sie bei der Erstellung Ihrer ersten odoo APP schnell zum Ziel führen.

Die odoo APPs in der cBUZZ OFFICE Suite definieren in der Datei /etc/odoo/openerp-server.conf den addons_path wie folgt:

addons_path = /usr/lib/python2.7/dist-packages/openerp/addons

Inside the a /usr/lib/python2.7/dist-packages/openerp/addons directory, I created an openacademy directory that has the following contents:

├── openacademy
│ ├── __init__.py # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein
│ ├── __openerp__.py # Enthält verschieden Paket Informationen
│ ├── course.py   # Das Modul mit ihren Objekt definitionen
│ ├── session.py # Wird unter odoo APP Teil 2 beschrieben
│ ├── static/ # Das static Verzeichnis wird angelegt, bleibt aber noch leer

│ ├── view/ # Definieren Sie hier ihre Ansichten (Aktionen, Wizards, Menüs, Masken)
│ │ ├── menu.xml


mkdir /usr/lib/python2.7/dist-packages/openerp/addons/openacademy
chmod -R 755 /usr/lib/python2.7/dist-packages/openerp/addons/openacademy

./openacademy/__init__.py   # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein:

import course

./openacademy/__openerp__.py oder
./openacademy/__odoo__.py               # the manifest file| Enthält verschieden Paket Informationen

{
    'name': 'Open Academy',
    'version': '1.0',
    'category': 'Tools',
    'summary': 'Courses, Sessions, Subscriptions',
    'description': """
Courses, Sessions, Subscriptions
================================

This module manages the definition of course, the organization,
sessions, and the subscriptions of participants.
""",
    'depends' : ['base'],
    'data' : ['view/menu.xml'],
    'images': [],
    'demo': [],
    'installable' : True,
    'application': True,
}

This is the model class. I changed the import statement and the parent class name to match the current Odoo 8.0 version:

./openacademy/course.py     # Das Modul mit ihren Objekt definitionen

from openerp import models, fields

class Course(models.Model):
    _name = 'openacademy.course'

    name = fields.Char(string='Title', required=True)
    description = fields.Text()

./openacademy/static/

  • Wir legen ein leeres static Verzeichnis an.


./openacademy/view/menu.xml     # Definiert das Menü und Maske

<?xml version="1.0" encoding="UTF-8"?>
<openerp>
    <data>

        <menuitem name="Open Academy" id="menu_root" sequence="110"/>

        <menuitem name="General" id="menu_general" parent="menu_root"/>

        <record model="ir.actions.act_window" id="action_courses">
            <field name="name">Courses</field>
            <field name="res_model">openacademy.course</field>
            <field name="view_mode">tree,form</field>
        </record>

        <menuitem name="Courses" id="menu_courses" parent="menu_general"
            sequence="1" action="action_courses"/>
    </data>
</openerp> 


When I re-started Odoo, it couldn't find the newly created module. I changed all openacademy file mode to 755. Retarted Odoo, still failed.


Wenn Sie alle Dateien angelegt haben, starten Sie odoo neu:

/etc/init.d/odoo restart

Im odoo Back-End kann der Admin die lokale Open Acadamy APP jetzt installieren.

Einstellungen ‣ Benutzer ‣ Benutzer

  • Den Benutzer auswählen und unter der Überschrift Bedienbarkeit das Feld Technische Eigenschaften aktivieren.
  • Danach unter Einstellungen ‣ ModuleUpdates Aktualisieren das neue APP bekannt machen.
  • Unter Einstellungen ‣ Module ‣ Lokale Apps bei Open Academy auf Installieren klicken.

Als Entwickler können Sie auch den Entwicklermodus aktivieren. Klicken Sie rechts oben Ihr Benutzerkonto an und wählen Sie Über odoo. Im neuen Fenster klicken Sie Entwicklermodus wählen.

Sauber entfernen

  • Wählen Sie unter Einstellungen ‣ Module ‣ Lokale Apps zuerst die APP Open Academy und klicken Sie auf Deinstallieren.
  • Wählen Sie unter Einstellungen ‣ Module ‣ Lokale Apps nochmals die APP Open Academy und wählen Sie oben in der Mitte Mehr ‣ Löschen.
  • Löschen Sie das Verzeichnis /usr/lib/python2.7/dist-packages/openerp/addons/openacademy
  • Danach unter Einstellungen ‣ ModuleUpdates Aktualisieren bekannt machen, dass es die die lokale APP Open Academy nicht mehr am Server gibt. 

PRAXIS: odoo APP Teil 2

This is the second part of the Open Adademy example. In part one we created a simple module layout. This part demonstrates model relationships, default values and the use of onchange api. All code are listed at the end of this blog. When making a change of an addon code, you may need to re-start Odoo or update the addon or do both. 

To show the model relationship usage, we first create a session class. The session class has two fields that are associated with two other models: 

  • the course attribute has a many-to-one relationship with the course model in the same module;
  • the instructor attribute has a many-to-one relationship with the res.partner model. 

We can also use default=value to define default value for an attribute.

@api.onchange is an interesting Python decorator that calls its decorated method whenever the course attribute is changed. 

Additionally, we add a sessions attribute in course model thus it can point to a list of its sessions. We changed its view file to show the list too. In the course model, the relationship of sessions is one-to-many.

Then in the Installed Modules, I was able to find Open Academy and install it. I could see the Open Academy at the top level menu, click it and then Course submenu, I was able to list courses (an empty list at the beginning) and create new courses as demonstrated by Raphael.

Source code:


./openacademy/__init__.py   # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein: 

import course
import session

./openacademy/__openerp__.py oder
./openacademy/__odoo__.py               # the manifest file| Enthält verschieden Paket Informationen

{
    'name': 'Open Academy',
    'version': '1.0',
    'category': 'Tools',
    'summary': 'Courses, Sessions, Subscriptions',
    'description': """
Courses, Sessions, Subscriptions
================================

This module manages the definition of course, the organization,
sessions, and the subscriptions of participants.
""",
    'depends' : ['base'],
    'data' : ['view/menu.xml'],
    'images': [],
    'demo': [],
    'installable' : True,
    'application': True,
}

This is the model class. In part 2 it has a new sessions attribute:

./openacademy/course.py     # Das Modul mit ihren Objekt definitionen

from openerp import models, fields

class Course(models.Model):
    _name = 'openacademy.course'

    name = fields.Char(string='Title', required=True)
    description = fields.Text()

    sessions = fields.One2many('openacademy.session', 'course')

This is the new model class that shows relationships, default values and onchange api:

./openacademy/session.py     # Definiert ...

from openerp import models, fields, api

class Session(models.Model):
    _name = 'openacademy.session'

    name = fields.Char(string='Title', required=True)
    start_date = fields.Date()
    duration = fields.Integer(help="Duration in days")
    seats = fields.Integer(string="Number of Seats")

    course = fields.Many2one('openacademy.course', required=True)
    instructor = fields.Many2one('res.partner')

    active = fields.Boolean(default=True)
    start_date = fields.Date(default=fields.Date.today)

    @api.onchange('course')
    def _onchange_course(self):
        if not self.name:
            self.name = self.course.name

./openacademy/static/

  • Wir legen ein leeres static Verzeichnis an.


In part 2, we added a session menu item and an action window for the session model:

./openacademy/view/menu.xml     # Definiert das Menü und Maske

<openerp>
    <data>

        <menuitem name="Open Academy" id="menu_root" sequence="110"/>

        <menuitem name="General" id="menu_general" parent="menu_root"/>

        <record model="ir.actions.act_window" id="action_courses">
            <field name="name">Courses</field>
            <field name="res_model">openacademy.course</field>
            <field name="view_mode">tree,form</field>
        </record>

        <menuitem name="Courses" id="menu_courses" parent="menu_general"
            sequence="1" action="action_courses"/>

        <record model="ir.ui.view" id="course_form">
            <field name="name">course form view</field>
            <field name="model">openacademy.course</field>
            <field name="arch" type="xml">

                <form string="Course" version="7.0">
                    <sheet>
                        <h1>
                            <field name="name" placeholder="Course Title"/>
                        </h1>
                        <notebook>
                            <page string="Description">
                                <field name="description"/>
                            </page>
                            <page string="Sessions">
                                <field name="sessions"/>
                            </page>
                        </notebook>
                    </sheet>
                </form>

            </field>
        </record>

        <record model="ir.actions.act_window" id="action_sessions">
            <field name="name">Sessions</field>
            <field name="res_model">openacademy.session</field>
            <field name="view_mode">tree,form</field>
        </record>

        <menuitem name="Sessions" id="menu_sessions" parent="menu_general"
            sequence="2" action="action_sessions"/>

    </data>
</openerp>


We didn't complete the whole demo as showed in Odoo Open Days video: Developing V8 Backend modules by Raphael Collet because there is too much material for a tutorial.

Im nächsten Abschnitt Sie das ganze Beispiel von Raphael Collet:


Der Open Days Beitrag von Raphael Collet auf Github

Anm.: Hier finden Sie ein korrigiertes Beispiel des GitHub Beitrages von Raphael Collet.

Agenda

  • Module Open Academy

Architecture of Odoo

  • Three-tier client/server/database

  • Web client in Javascript

  • Server and backend modules in Python
    • MVC framework

Module Open Academy

The Module

  • Manage courses, sessions, and subscriptions

  • Learn
    • Structure of a module
    • Definition of data models
    • Definition of views and menus

Structure of a Module

An Odoo module is
  • a python module (data models), with
  • a manifest file,
  • XML and CSV data files (base data, views, menus),
  • frontend resources (Javascript, CSS).

The Open Academy Module

Inside the a /usr/lib/python2.7/dist-packages/openerp/addons directory, I created an openacademy directory that has the following contents:

├── openacademy
│ ├── __init__.py # Der Einsprungpunkt für ihr Modul und alle weiteren Python Module
│ ├── __openerp__.py # Enthält verschieden Paket Informationen
│ ├── course.py  # Das Modul mit ihren Objekt definitionen
│ ├── session.py # Wird unter odoo APP Teil 2 beschrieben
│ ├── static/ # Das static Verzeichnis wird angelegt, bleibt aber noch leer

│ ├── view/ # Definieren Sie Ihre Ansichten (Aktionen, Wizards, Menüs, Masken)
│ │ ├── menu.xml


mkdir /usr/lib/python2.7/dist-packages/openerp/addons/openacademy

./openacademy/__init__.py   # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein

import course

./openacademy/__openerp__.py oder
./openacademy/__odoo__.py                # the manifest file| Enthält verschieden Paket Informationen

{
    'name': 'Open Academy',
    'version': '1.0',
    'category': 'Tools',
    'summary': 'Courses, Sessions, Subscriptions',
    'description': """
Courses, Sessions, Subscriptions
================================

This module manages the definition of course, the organization,
sessions, and the subscriptions of participants.
""",
    'depends' : ['base'],
    'data' : ['view/menu.xml'],
    'images': [],
    'demo': [],
    'installable' : True,
    'application': True,
}

The Course Model

A model and its fields are defined in a Python class:

./openacademy/course.py     # Das Modul mit ihren Objekt definitionen

from odoo import Model, fields

class Course(Model):
    _name = 'openacademy.course'

    name = fields.Char(string='Title', required=True)
    description = fields.Text()

The Menu as XML data

./openacademy/view/menu.xml     # Definiert das Menü und Maske

<?xml version="1.0" encoding="UTF-8"?>
<openerp>
    <data>

        <menuitem name="Open Academy" id="menu_root" sequence="110"/>

        <menuitem name="General" id="menu_general" parent="menu_root"/>

        <record model="ir.actions.act_window" id="action_courses">
            <field name="name">Courses</field>
            <field name="res_model">openacademy.course</field>
            <field name="view_mode">tree,form</field>
        </record>

        <menuitem name="Courses" id="menu_courses" parent="menu_general"
            sequence="1" action="action_courses"/>
    </data>
</openerp>


Let's add a Form View

<record model="ir.ui.view" id="course_form">
    <field name="name">course form view</field>
    <field name="model">openacademy.course</field>
    <field name="arch" type="xml">

        <form string="Course" version="7.0">
            <sheet>
                <h1>
                    <field name="name" placeholder="Course Title"/>
                </h1>
                <notebook>
                    <page string="Description">
                        <field name="description"/>
                    </page>
                </notebook>
            </sheet>
        </form>

    </field>
</record>


The Session Model

./openacademy/session.py     # Definiert ...

class Session(Model):
    _name = 'openacademy.session'

    name = fields.Char(required=True)
    start_date = fields.Date()
    duration = fields.Integer(help="Duration in days")
    seats = fields.Integer(string="Number of Seats")

Relational Fields

Let us link sessions to courses and instructors:

class Session(Model):
    _name = 'openacademy.session'

    ...

    course = fields.Many2one('openacademy.course', required=True)
    instructor = fields.Many2one('res.partner')

Let us back-link courses and sessions:

class Course(Model):
    _name = 'openacademy.course'

    ...

    responsible = fields.Many2one('res.users')
    sessions = fields.One2many('openacademy.session', 'course')

Let us link sessions to partners for attendee subscription:

class Session(Model):
    _name = 'openacademy.session'

    ...

    attendees = fields.Many2many('res.partner')

Computed Fields

The value of those fields is computed:

class Session(Model):
    _name = 'openacademy.session'

    ...

    taken_seats = fields.Float(compute='_compute_taken_seats')

    @api.one
    @api.depends('attendees', 'seats')
    def _compute_taken_seats(self):
        if self.seats:
            self.taken_seats = 100.0 * len(self.attendees) / self.seats
        else:
            self.taken_seats = 0.0

About self

Model instances are recordsets.

A recordset is an hybrid concept:
  • collection of records
  • record

for session in self:
    print session.name
    print session.course.name

assert self.name == self[0].name

Feedback with "Onchange" Methods

Modify form values when some field is filled in:

class Session(Model):
    _name = 'openacademy.session'

    ...

    @api.onchange('course')
    def _onchange_course(self):
        if not self.name:
            self.name = self.course.name

Default Values

Specify the initial value to use in a form:

class Session(Model):
    _name = 'openacademy.session'

    ...

    active = fields.Boolean(default=True)
    start_date = fields.Date(default=fields.Date.today)

    ...

Model Constraints

Prevent bad data:

from odoo.exceptions import Warning

class Session(Model):
    _name = 'openacademy.session'

    ...

    @api.one
    @api.constrains('instructor', 'attendees')
    def _check_instructor(self):
        if self.instructor in self.attendees:
            raise Warning("Instructor of session '%s' "
                "cannot attend its own session" % self.name)

More Stuff

  • Extend existing models
  • Many view types
  • Workflows
  • Reports
  • Security
  • Translations

Backend Modules in V8

Conclusion

  • Modules have a simple structure

  • Model definition intuitive and efficient
    • uses Python standards (decorators, descriptors)
    • recordsets provide support for "batch" processing
    • many model hooks (default values, constraints, computed fields)

To let my code find other odoo modules, I added the following line to the .profile of odoo user account (the account used to run Odoo). 

odoo Web APPs

Odoo Front-End Module, die mit den odoo Back-End Modulen kommunizieren

(Quelle: Odoo) Seit der Version V8 verkürzt odoo den Prozess zur Erstellung von odoo Frontend-Modulen (zB. Internet Websites) die leicht mit den odoo Back-End-Modulen (zB. ERP, CRM, Buchhaltung) zusammenarbeiten können.

Der Ausdruck Front-End und Back-End wird hier aus der Sicht des odoo Anwenders verwendet.

  • Das Back-End ist in der Regel für die Mitarbeiter des Unternehmens zugänglich und dient beispielsweise den Vertriebsmitarbeitern bei der Verwaltung der Rechnungen, Einkäufe, des Lagers usw.

  • Der Begriff Front-End wird hier beispielsweise für die öffentliche zugängliche Website des Unternehmens verwendet. Es gibt viele Standard Front-End-Module als odoo APPs zum download. Sie sind leicht zu erkennen, weil ihr Name immer das Präfix website_  enthält.

Odoo kann aber auch einfach als Standalone-Web Application Framework eingesetzt werden - ähnlich wie Djangohttps://www.djangoproject.com/ oder Flaskhttp://flask.pocoo.org/ - ohne den Back-End-Teil zu berühren.

Die besondere Stärke von odoo ist es aber, ein modernes Web Application Framework zu sein, dass letztlich die Entwicklungszeit für Projekte stark reduziert, die sowohl Back-End als auch Front-End-Funktionen erfordern. In odoo braucht dafür keine spezielle Middleware (Konnektoren) mehr programmiert werden, damit die einzelnen Systemen miteinander kommunizieren können.

Wie alle modernen Web-Applikations-Frameworks verwendet auch odoo das MVC Modell (Model View Controller) als Basis für die Strukturierung sowohl der Front-End-Module als auch der Back-End-Module. In diesem Beitrag werden wir zeigen, wie der Controller und die Front-End-Ansichten (Views als Qweb Vorlagen) zusammen arbeiten. Später werden wir Sie bei der Erstellung eines kompletten Frontend-Moduls in odoo begleiteten, indem wir eine einfache Blog-Anwendung erstellen. 

Model View Controller

Der "MVC Modell" Bereich wird in diesem Beitrag nicht behandelt, alle Interessierten finden dazu aber andere Ressourcen im Web. Wie das neue Modell API funktioniert wurde bereits bei den OpenDays 2014https://github.com/nbessi/odoo_new_api_guideline beschrieben und wird auf odoo's Website zu Technische Dokumentationhttps://www.odoo.com/documentation/8.0/ ständig ergänzt.

Für die weiteren Ausführungen sollten Sie bereits ein grundlegendes Wissen zum Erstellen und Ausführen von odoo Module besitzen.
  1. Experimentieren mit den Controllern
  2. Spaß mit den Vorlagen (Templates)
  3. Die Blog APP

Experimentieren mit den Controllern

In Web-Anwendungen sind die Controller einfach HTTP-Handler, die definieren was passiert, wenn in einer HTTP-Anfrage ein "Treffer" auf einen bestimmten Pfad auf unserem Server zeigt. Odoo verwendet die werkzeughttp://werkzeug.pocoo.org/ Bibliothek für die Umsetzung des Controllers. Sie können also in der Dokumentation zur werkzeug5 Bibliothek jederzeit nachlesen, um die volle Leistungsfähigkeit der Bibliothek nutzen zu können.  

Einen kurzen Überblick über die Controller in odoo finden Sie mit diesen einfachen Beispielen.

Hier ist einfaches Beispiel eines Controllers (einer Steuerung), die nur die Nachricht "Hallo Welt" ausgibt:

      from openerp.http import Controller, route

class TestControllers(Controller):
    @route("/hello/")
    def hello(self):
        return "Hello World"
    


As we can see the controller is just a method of a class which inherits from openerp.http.Controller and is wrapped using the decorator function openerp.http.route.

If we open our browser and go to ‘localhost:odoo_port/hello/’ we will see our message.

The returned string is wrapped inside a proper HTML document so you can put there any valid HTML and it will be rendered correctly in the browser. Example:

      @route("/hello/") 
def hello(self): 
    return "<strong>Hello World</strong>"
    


This will make the previous message bold as expected.

Routes can also be specified as patterns and the variable parts will be supplied as parameters to the controller method. Example:

      @route("/hello/<name>/")
def hello(self, name):
    return "Hello %s!" % name

    

These arguments will always be represented as strings in Python but if you know that the supplied parameter needs to be something other than a string we can use convertors. Example:

      @route("/double/<int:number>/")
def double(self, number):
    return "%d * 2 = %d" % (number, number * 2)

    

The converters are extendable you can create your own if you like. There is more info about them in the werkzeug docs.

Odoo has defined its own converter for the Model Records. Here is an example:

      @route("/user/<model('res.users'):user>/")
def user_name(self, user):
    return user.name

    

You can test it out by going to this route /user/res-users-1-1/ You should see the name of the Administrator user.

We can also control the access for a specific controller by using the auth keyword parameter on the route decorator. Example:

      @route("/hello/<name>/", auth="user")
def hello(self, name):
    return "Hello %s!" % name

    

There are three possible values for auth: user, public, none. When setting auth to user it means that the user must be logged in to access the controller. This is the default value, so when we don’t specify the auth parameter user authentication is used. If we set it to public it means that even not logged in users can access the controller. If the user is not logged in then he will be assigned to the Public user group. So we can specify different access rights for these users if we like.

For example if there is possibility to enter some data for some model using that controller, we can give the Public user group only read rights on that model and they will have to log in if they want to enter some data otherwise they will get a 403 HTTP response code. The other option is to use ‘none’ which just means that if the user is not logged in he wont be assigned to any group and the controller can be accessed by anyone, same as for public.

Templating Fun

Before v8 if you have tried to develop web apps in Odoo you had to integrate it yourself with some existing templating system like Jinja or Mako. But QWeb templates really facilitate what Odoo is great for. That is it allows developers, or even users in this case, to easily change what other developers before them have created. This is because the QWeb templates are XML documents which are stored in the database, same as the other back-end views in Odoo, so they can be changed even after they have initially been imported/created.

Vor der Version odoo v8 konnte man Web-Anwendungen in odoo nur entwickeln, wenn man selbst Template-Systeme wie Jinja oder Mako integrierte. Mit den Qweb Vorlagen geht odoo v8 einen neuen großartigen Schritt. Jetzt können Entwickler und sogar Anwender auf einfache Weise ändern, was andere Entwickler vor ihnen geschaffen haben. Die odoo Qweb Vorlagen sind einfach XML-Dokumente, die in der Datenbank gespeichert werden, wie die anderen odoo Back-End-Ansichten auch. Nachdem die Vorlagen zunächst importiert/erstellt wurden, können sie jederzeit und rasch verändert werden.

This is a different approach from other templating systems where the template files are basically just static files which you can’t really change that much except in places where the original developer had made possible.

Das ist ein anderer Ansatz, als der von anderen Template-Systeme bekannte Ansatz, bei denen die Vorlagendateien im Grunde nur statische Dateien sind, die Sie nicht wirklich einfach können, außer an den Stellen, wo der ursprüngliche Entwickler die Vorlagen gespeichert hat.

We are not going to cover here how to inherit and change other templates but it is very similar to how view inheritance is done for the back-end views, that is just supply an inherit_id and use XPath to change the inherited template.

To define a QWeb template we just create the following XML structure:

      <template id="template_id" name="Sample Template">
    ...
</template>
    

To use a template as the response from a controller, instead of just returning the naked HTML string, we need to make a call to the request.render method.

      # Controller

@route("/hello//")
def hello(self, name):
    return request.render("module_name.template_id", {'name': name})

    
      <!--Template-->

<template id="template_id" name="...">
    Hello <t t-esc="name" />!
</template>

<!--GET /hello/Jane-->
<!--Result: Hello Jane!-->

    

The first parameter that is passed to the method is the id of the template which must always be classified with the module name where our template is defined. The other parameter is a dictionary where we can define variables that can later be called from within the template itself.

I’m going to give a short overview of the QWeb syntax.

QWeb uses “magical” XML tag attributes to control how the templates are rendered. We can divide these in four groups: output, control flow, functional and assignment attributes.

Output attributes are used to render some value in the template:

t-raw, renders the value of a python expression. Example:

      <t t-raw="'Hello' + '!' * 3" />

<!--Result: Hello!!!-->

    

t-esc, same as t-raw but it does HTML escaping on the final value of the expression. This one is safer and should be used always when rendering values that can be provided from users of the website because it will prevent the possibility of cross-stie scripting (XSS) attacks.

t-att-*, used to dynamically generate the value of an XML tag attribute. The * can be substituted for any attribute, like for example class, id, name, etc. It is also possible to use the t-attf-* attribute which uses special formattingsyntax that is very similar of how Jinja renders variables. Example:

      # Controller
# ...
post_id = 1
request.render(template_id, {"post_id": post_id})

    
      <!--Template-->

<div t-att-id="'post-' + str(post_id)" />

<!--or simpler-->

<div t-attf-id="post-{{ post_id }}" />

<!--Result: <div id="post-1" />-->

    

Control flow attributes which enable to do branching and looping within the template.

t-if, behaves exactly as the python if statement and if it evaluates to True then the tag and all subtags will be rendered. There is no construct for the else statement but this can easily be simulated by doing a negation (not) of the expression from another t-if attribute. Example:

      # Controller
# ...
request.render(template_id, {"test": True})

    
      <!--Template-->

<t t-if="test">
    First condition
</t>
<t t-if="not test">
     Else condition
</t>

<!--Result: First condition-->

    

t-foreach and t-as, these two are used to define a for loop. For example:

      <t t-foreach="[1,2,3]" t-as="digit">
    <t t-esc="digit" />
</t>

<!--Result: 1 2 3-->

    

It is possible to also define new variables within the template context using the assignment attribute t-set and t-value. Example:

      <t t-set="message" t-value="'Hello' + '!!!'" />
<t t-esc="message" />

<!--Result: Hello!!!-->

    

t-valuef can also be used here and using the previous example it should look like this:

      <t t-set="message" t-valuef="Hello{{ !!! }}" />
<t t-esc="message" />

<!--Result: Hello!!!-->

    

As you have noticed in the examples there is also a special tag <t/> which can be used if we don’t want the result nested within some tag since the <t/> tag will be omitted from the resulting HTML output. But we can also use any other tag and the result will be nested inside it:

      <div t-set="message" t-valuef="Hello{{ !!! }}" />
<div t-esc="message" />

<!--Result: <div>Hello!!!</div>-->

    

We can also use templates as functions and call them from within other templates. They will be rendered in the place where the template was called. The functional attributet-call is used to call templates and the value of this attribute should be a fully qualified template id. Example:

      # Controller
# ...
request.render(module_name.main, {})

    
      <!--Template-->

<template id="message" name="...">
     <strong>Hello!!!</strong>
</template>

<template id="main" name="..." />
    <div class=”wrapper”>
        <t t-call="module_name.message">
    </div>
</template>

<!--Result: <div class=”wrapper”><strong>Hello!!!</strong></div>-->

    

Blog Application

Now that we have the basics down, we can start with making a complete web app in Odoo.

Die odoo APPs in der cBUZZ OFFICE Suite definieren in der Datei /etc/odoo/openerp-server.conf den addons_path wie folgt:

addons_path = /usr/lib/python2.7/dist-packages/openerp/addons

Then inside the a /usr/lib/python2.7/dist-packages/openerp/addons directory, I created an odoo_blog directory that has the following contents:

To start we should create this directory structure in our file system.

├── odoo_blog
│ ├── __init__.py # Der Einsprungpunkt für ihr Modul, hier binden sie alle weiteren Python Module ein
│ ├── __openerp__.py # Enthält verschieden Paket Informationen
│ ├── static/ # Bleibt leer
│ ├── model
│ │ ├── __init__.py
│ │ ├── post.py
│ └── view # Definieren Sie hier ihre Ansichten (Aktionen, Wizards, Menüs, Masken)
│ │ ├── posts.xml
│ ├── controller # Definieren Sie hier Ihre Objekte
│ │ ├── __init__.py
│ │ ├── posts.py

The __openerp__.py file should something look like this:

      {
    # ...
    'description': """
A simple blog application that allows
for creating, editing, deleting and viewing
blog posts.
""",
    'depends': [
        'website',
    ],
    'installable': True,
    'application': False,
}

    

The important thing to note here is that front-end modules should depend on the website module. It is possible also to develop front-end modules or standalone web applications without inheriting from website, since the web application framework capabilities are all in the base core odoo module.

It is advisable to inherit from website whenever you want create front-end modules which are more closely integrated with the back-end. But even if the module inherits from website you are still able to control which controllers (no pun intended) use website.

Now that we have the proper skeleton Odoo module structure in place we can start by creating our model which will hold the the necessary data for our blog.

In our simple application a blog is just a collection of posts so we can start by creating a model that will hold data for them. Create a file in the model directory and name it post.py. Here is how it should look:

      #file: odoo_blog/model/post.py

from openerp.models import BaseModel
from openerp import fields

class Post(BaseModel):

    _name = 'blog.post'

    title = fields.Char('Name')
    content = fields.Text('Content')

    

This model doesn’t implement any methods because for our simple application the standard methods like search, write and unlink from the inherited BaseModel are enough.

After we have created our model we can start working on our HTTP controllers. This is how the controller for listing all posts should look like:

      #file: odoo_blog/controller/posts.py

from openerp.http import Controller, route, request


class Posts(Controller):

    @route('/posts/', website=True)
    def list(self):
        posts = request.env['blog.post']
        posts = posts.search([])
        return request.render('odoo_blog.posts_list',
                              {'posts': posts}) 

    

The first thing that we can see is the decorator @route used on the method. The only required argument for the decorator is of course the route/url path itself which binds to this controller. 

The keyword argument website in the example is optional as we noted earlier and should be used only when the module that you are working on depends on the website module. 

The body of the controller method is also very simple and uses the new Odoo model api, which we are not going to cover in this post. So in short the first two statements just retrieve all the posts that are already created. At the end we just call the posts_list template and we pass to its context all the posts that are entered so far.

The next task is to create a view / template for the list controller (/posts) that we described earlier. 

Here is how it should look:

      <!--file: odoo_blog/view/posts.xml-->

<template id="posts_list" name="Blog Posts">
    <t t-call="website.layout">
        <div class="row">
            <div class="col-md-10">
		<div class="col-md-2">
                <a href="/posts/new/" class="btn btn-primary pull-right" role="button">Create</a>
            </div>
                <t t-foreach="posts" t-as="post">
                    <t t-call="odoo_blog.post_render"/>
                </t>
            </div>
        </div>
    </t>
</template>

    

Of course the __openerp__.py should be updated also with the template path.

      {
    # ...
    'data': [
        'view/posts.xml'
    ]
    # ...
}

    

The first thing that we notice is that a t-call is made to the website.layout template. This is one of the main benefits of using the website module. That is we can use the already existing templates there which already integrate the front-end and the back-end part of Odoo. The body of the t-call will be set to the “magic” variable 0. We can see the place where the contents of the t-call body will be rendered in this sample from the official website module:

      <!--file: website/views/webiste_templates.xml-->

<template id="layout" name="Main layout">
    ...
    <body>
        ...
        <main>
            <t t-raw="0"/> <!--Your HTML is injected here-->
        </main>
        ...
    </body>
    ...
</template>

    

The website.layout template uses bootstrap3 so we use it to style our templates as well. Another thing is the “a” link tag which points to the posts/new route. This will be another controller which we will implement later. Next we can see a for loop on the posts RecordSet and inside i it we make another t-call to a template within our own module. This is how that template looks:

      <template id="post_render" name="Post">
    <div class="media">
        <div class="media-body">
            <h4 class="media-heading" t-esc="post.title" />
            <p t-esc="post.content" />
            <div class="btn-group">
                <a t-attf-href="/posts/{{ slug(post) }}/view/" class="btn btn-default btn-xs" role="button">View</a>
                <a t-attf-href="/posts/{{ slug(post) }}/edit/" class="btn btn-default btn-xs" role="button">Edit</a>
                <a t-attf-href="/posts/{{ slug(post) }}/delete/" class="btn btn-default btn-xs" role="button">Delete</a>
            </div>
        </div>
    </div>
</template>

    

Inside this template the title and the content of the post are rendered using the t-esc attribute.
We can see there that we use the post variable to access the post data and we haven’t defined anywhere in this template what post actually is. This works because the template is called from another template which defined post, in the for loop we saw. So the template context is passed from one template to another when we make a t-call and we can use the called template just like a function by setting the value of the defined “parameters” inside it.

The link tags, or buttons, point to controllers that we haven’t defined yet. We can see from this tags how we dynamically set the href attribute by using the t-attf-href form and substitute inside it the post url slug. The slug function is passed insider the template context whenever you use the website model, website=True, so you just pass in it an Object/Record and it will give the correct url slug which should be used whenever you use the Model converter in the controller route path.

So now we can finally test what we have done so far by updating our module first  (restart the server also) and then point the our browser to /posts. We should see the a navigation menu and also some default text in the footer. This is all defined by the website.layout template which we are calling.  So we can’t see any posts, this is because we haven’t created any so far. 

Now we need to create the controller for handling the creation of posts. Here is how it should look:

      @route('/posts/new/', website=True)
def new(self, **form_data):
    form = PostsNewForm(request.httprequest.form)
    if request.httprequest.method == 'POST' and form.validate():
        posts = request.env['blog.post']
        posts.create({
            'title': form_data.get('title', ''),
            'content': form_data.get('content', ''),
        })
        return redirect("/posts/")
    return request.render('odoo_blog.posts_new', {'form': form})


from wtforms import Form, StringField, TextAreaField, validators


class PostsNewForm(Form):
    title = StringField('Title', [validators.Required()])
    content = TextAreaField('Content', [validators.Required()])

    

And the templates:

      <template id="posts_new" name="New Post">
    <t t-call="website.layout">
        <div class="row">
            <div class="col-md-6 col-md-offset-2">
                <form role="form" action="/posts/new" method="POST">
                    <t t-foreach="form" t-as="field">
                        <t t-call="odoo_blog.field_render" />
                    </t>
                    <button type="submit" class="btn btn-primary mt8">Save</button>
                </form>
            </div>
        </div>
    </t>
</template>

<template id="field_render" name="Render Form Field">
    <div t-attf-class="form-group {{ field.errors and 'has-error' or '' }}">
        <t t-raw="field.label" />
        <t t-raw="field(class_='form-control')" />
        <span t-foreach="field.errors" t-as="error" class="help-block">
            <t t-esc="error" />
        </span>
    </div>
</template>

    

This controller is more interesting because it has to deal with both GET and POST requests.
When we go to the Create New Post page we can see an HTML form there. This is the result of the GET request sent automatically by the browser when we try to open a page. So the handler just displays the empty form then. But when we try to Save the data in the form by creating a new post we will send a POST HTTP request to the same controller. So the controller needs to distinguish between these two types of requests and also validate the data sent by the form. If the data is correct then it will create a new post from it and redirect the user to /posts page. Form validation is a very common thing when creating web apps so we are using the wtforms library for this task. It is very easy and intuitive to use so you can check their documentation to learn the basics.

The form object is always passed to the template and by using it we can render the HTML form and also show if any errors were caught when the user tried to create a post. We have defined a separate template, field_render, that deals with rendering of the form fields. It is fairly easy to understand and basically the field knows how to render itself so we can just call it and pass any attribute that we wish to change in its HTML, in our case we want the field to have the bootstrap class form-control so that it displays properly. Just note that we are using t-raw for getting the value of the field because we don’t want the HTML to be escaped as it would be if we used t-esc.

So now we have the ability to create and list all posts. We just need to implement the Edit and Delete handlers and we will have our basic CRUD Blog application. But we are going to leave this part for you and if you like you can always check our git repository that we made for this example. 

Gesammelte Antworten aus dem odoo Forum

Installation von oddo APPs

Frage:

Fehlermeldung: Bei deinstallieren eines Moduls kommt die Meldung:

Integrity Error
The operation cannot be completed, probably due to the following:
- deletion: you may be trying to delete a record while other records still reference it
- creation/update: a mandatory field is not correctly set
[object with reference: ir.model.constraint - ir.model.constraint] 

Anwort im Forum:

The reason of this behaviour is as follows,

Earlier you might have added one referential field and restarted the server so that field ( for example, Many2One ) created with constraint of foreign key in database. Now for some reason you changed the column name or delete that column, so the reference of that constraint is still there in postgresql database. 

Now when you going to uninstall the module, system will go to clear ir.model & ir.model.fields entries, which are belongs to that module. But the column which is not available in py but constraint will be there under ir.model.constraint model. 

The solution of this could be,

  • 1) Go to Settings >> Technical >> Database Structure >> Model Constraints 
  • 2) Find the constraint in the Module, which you are going to uninstall.
  • 3) Delete the constraint.
  • 4) Uninstall the module.

It should be work now unless you are fail to explain your problem clearly. I am sure this will help you!