How send "email" from code?

Hi again :wink: Ill try to explain the situation.
My first try i try to create on “notification_email” module an a new entry to send email to purchase petitioners when the purchase “state” change to processing. In my case each purchase line may come from two different petitioners, and i want to send diferent email to each one (only with own petition lines).
On the notification_email trigger i use “on update” but when purchase process, the purchase change the state but the trigger dont do nothing. I use diferent breakpoints on Trigger class but never go to this class. I think " On update" doesnt work properly, dont know exactly.

To try to solve my problem I try to implement replicating the same way that i can see on “tests” without success :frowning:

Accept ideas to solve this …

Actually i load the “record” on HTML template but …

code.

with Transaction().set_context(lines_to_email=lines):
                prueba = get_email(_REPORT, purchases[0], language)

            from_ = config.get('email', 'from')
            to_ = [employee]

            from email.mime.text import MIMEText
            msg = MIMEText(prueba, _charset='utf-8')
            msg['From'] = from_
            msg['To'] = to_
            msg['Cc'] = 'user@email.es'
            msg['Subject'] = Header('ENVIO FORZADOP', 'utf-8')
            msg['Auto-Submitted'] = 'auto-generated'
            msg['Message'] = prueba

            # sendmail_transactional(
            #     from_, to_, msg, datamanager=datamanager)
            sendmail(from_, to_, msg)
Traceback (most recent call last):
  File "/home/jonatan/tryton/trytond/trytond/protocols/dispatcher.py", line 186, in _dispatch
    result = rpc.result(meth(*c_args, **c_kwargs))
  File "/home/jonatan/tryton/trytond/trytond/model/modelview.py", line 692, in wrapper
    return func(cls, records, *args, **kwargs)
  File "/home/jonatan/tryton/trytond/trytond/modules/electrans/purchase.py", line 113, in process
    super(Purchase, cls).process(purchases)
  File "/home/jonatan/tryton/trytond/trytond/modules/production_subcontract/production.py", line 305, in process
    super(Purchase, cls).process(purchases)
  File "/home/jonatan/tryton/trytond/trytond/model/modelview.py", line 692, in wrapper
    return func(cls, records, *args, **kwargs)
  File "/home/jonatan/tryton/trytond/trytond/modules/sale_supply/purchase.py", line 61, in wrapper
    func(cls, purchases)
  File "/home/jonatan/tryton/trytond/trytond/modules/sale_supply/purchase.py", line 74, in process
    super(Purchase, cls).process(purchases)
  File "/home/jonatan/tryton/trytond/trytond/model/modelview.py", line 692, in wrapper
    return func(cls, records, *args, **kwargs)
  File "/home/jonatan/tryton/trytond/trytond/modules/electrans_notification_email/purchase.py", line 48, in process
    msg = MIMEText(prueba, _charset='utf-8')
  File "/usr/lib/python3.8/email/mime/text.py", line 42, in __init__
    self.set_payload(_text, _charset)
  File "/usr/lib/python3.8/email/message.py", line 321, in set_payload
    self.set_charset(charset)
  File "/usr/lib/python3.8/email/message.py", line 364, in set_charset
    payload = payload.encode('ascii', 'surrogateescape')
AttributeError: 'tuple' object has no attribute 'encode'

Maybe exist any easy way, comment that my Tryton version is 5.2

& Thanks in advance :wink:

You should use a “On write” trigger and test that the state is processing on condition. Something like:

Eval('self', {}).get('state') == 'processing

That should do the trick for sending the email when processing.

How do you store the petitioners on the line? If the petitioner is a Many2One to a known module the field should be already available as recipient on the notification trigger.

Ill try thx

Get the petitioners not a problem :
purchase.line > purchase.request > purchase.requisition < petitioner

The problem of trigger as i try to explain is that in purchase i have for instance 3 lines, each line from diferent petitioner. I want to send 3 diferent mails to each one ( linked to them requisition).
I tryed different ways to send email using html template (similar to notification_email), but doesnt works.
Maybe the On Write trigger works, but send a unique mail for all petitioners for instance, but no solve my problem. Dont know if u can help me to try to send a email from code?

Thanks again @pokoli :slight_smile:

Ok, so in your case you need to notify the employee of the requisition, which makes me think that the model triggering the email should be the requisition and not the purchase.

I will suggest adding a new field on requistion to know if the purchase have been processed (you will need to update it when processing the purchase) and then use the notification_email module to send and email based on this new field.

This way each petitioner will receive an email for each of it’s requisitions when they are purchases.

Hope it helps!

Any way exist the posibilty that the requisition parcially completed. i would use this field but if for instance i complete a line and tomorrow completes the second line from same requistion. The trigger send me 2 email, first with 1 line and the following the first line + second. Can u understand me? dont know if i explained correctly.

Thanks again!

In such case, probably the best is to not update the field until it is fully completed, but if you need several emails you can have several states to trigger diferent emails.

It all depends on your needs.

I tryed to use the trigger with “On Write” & “On Update”, and i put several “breakpoints” into Trigger class. Always stop on breakpoint when i create a purchase requisition but never when update the state, the descriptions for instance…
Maybe is a bug of 5.2 vers?

Which condition are you using?

[Equal(Get(Eval(‘self’, {}), ‘state’, ‘’), ‘done’)]
But as i tell u i try to use in some breakpoint method that i think that Evaluate. Think that breakpoint places its correct because on create purchase requisition always stop but not on update.

Thx!

Edit: U think its posible to send mail by code??

For me we must first clarify something because there is no “On Update” on trigger.
If you mean “On Time” then you should not check it because it is exclusive with “On Write”.

So for me the trigger should be placed on the purchase line and not the purchase or maybe on the purchase requisition but it will need to setup some write that can be triggered.

“On write” Sorry :slight_smile: i try to explain again with screenshoot.


On create always send the mail but “On write” is never working. I tested with any breakpoint on Trigger methods and never pass through this code to check conditions of the trigger.
I think that doesnt work properly maybe is an issue related to the version??

I guess you put the break point in the wrong place.
The first point is that writing on the record should be checked by ModelStorage.trigger_write_get_eligibles.

2 Likes

You were right! Following from that point what I have verified is that eval condition at first iteration response True when it should be FALSE, to at second iteration detect change.

https://hg.tryton.org/trytond/file/5.2/trytond/model/modelstorage.py#l234

I tryed some condition to change that first eval without success because the first iteration is returning always True.

Thanks again :slight_smile:

So you create the record with the condition already evaluated to True. In this case you must define the trigger to be “On Create” and “On Write” (which is a common practice).

I created fer more triggers to try to understand how it works… But i never make it works…

Debugging from you pointed me i arrived to ir.trigger.Eval method whis is returning always true regardless the arguments

https://hg.tryton.org/trytond/file/5.2/trytond/ir/trigger.py#l174

I think that the problem maybe is on env[‘self’] , because “EvalEnvironment(record, record.class)” is always returning “” (an empty string). It does not matter what model you pass an argument.
I also saw that EvalEnvironment CLASS has changeD in the newest versions of Tryton so it make me think that maybe the issue can be related. Anyone can confirm me that on write triggers are working on v5.2?

I can confirm it is working on supported series (5.0, 6.0 and 6.2) see https://drone.tryton.org/tryton/trytond for test_on_write.

1 Like

Hi again :wink: after we continue trying to do the trigger ‘write’ works. i see on newest versions of Tryton on triggers.py have a new method ‘_record_eval_pyson(…’ I added this method on my triggers.py and I change the return value of “eval” function and “Voila!!” Works :partying_face: :partying_face:

Dont know if this solution u implemented on newest version. I leave the git diff by if u want to check.
Again thx :wink:

diff --git a/trytond/ir/trigger.py b/trytond/ir/trigger.py
index 2487307b..02938c50 100644
--- a/trytond/ir/trigger.py
+++ b/trytond/ir/trigger.py
@@ -7,6 +7,8 @@ from sql.aggregate import Count, Max
 
 from trytond.model.exceptions import ValidationError
 from trytond.i18n import gettext
+from trytond.pyson import PYSONEncoder
+
 from ..model import (
     ModelView, ModelSQL, DeactivableMixin, fields, EvalEnvironment, Check)
 from ..pyson import Eval, PYSONDecoder
@@ -172,7 +174,7 @@ class Trigger(DeactivableMixin, ModelSQL, ModelView):
         env['time'] = time
         env['context'] = Transaction().context
         env['self'] = EvalEnvironment(record, record.__class__)
-        return bool(PYSONDecoder(env).decode(trigger.condition))
+        return _record_eval_pyson(record, trigger.condition, True)
 
     @classmethod
     def trigger_action(cls, records, trigger):
@@ -304,3 +306,19 @@ class TriggerLog(ModelSQL):
 
         table = cls.__table_handler__(module_name)
         table.index_action(['trigger', 'record_id'], 'add')
+
+
+def _record_eval_pyson(record, source, encoded=False):
+    transaction = Transaction()
+    if not encoded:
+        pyson = _pyson_encoder.encode(source)
+    else:
+        pyson = source
+    env = EvalEnvironment(record, record.__class__)
+    env['context'] = transaction.context
+    env['active_model'] = record.__class__.__name__
+    env['active_id'] = record.id
+    return PYSONDecoder(env).decode(pyson)
+
+
+_pyson_encoder = PYSONEncoder()