send email to sender of unrecognized file
The email processing sends a notification message to the person who sent a unrecognized file to the workouts@rowsandall.com email address. This way, people will know that their email has worked and perhaps stop sending 99 or more emails with the same unsupported file.
This commit is contained in:
@@ -92,14 +92,22 @@ def make_new_workout_from_email(rower, datafile, name, cntr=0):
|
|||||||
# shutil.copyfileobj(f_in,f_out)
|
# shutil.copyfileobj(f_in,f_out)
|
||||||
fcopy = "media/"+datafilename
|
fcopy = "media/"+datafilename
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
res = handle_sendemail_unrecognized.delay(fcopy,
|
res = handle_sendemail_unrecognized.delay(
|
||||||
rower.user.email)
|
fcopy,
|
||||||
|
rower.user.email
|
||||||
|
)
|
||||||
|
res = handle_sendemail_unrecognizedowner.delay(
|
||||||
|
rower.user.email,
|
||||||
|
rower.user.first_name
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
res = queuehigh.enqueue(handle_sendemail_unrecognized,
|
res = queuehigh.enqueue(handle_sendemail_unrecognized,
|
||||||
fcopy,
|
fcopy,
|
||||||
rower.user.email)
|
rower.user.email)
|
||||||
|
res = queuehigh.enqueue(handle_sendemail_unrecognizedowner,
|
||||||
|
rower.user.email,
|
||||||
|
rower.user.first_name
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
summary = ''
|
summary = ''
|
||||||
|
|||||||
111
rowers/tasks.py
111
rowers/tasks.py
@@ -1,4 +1,4 @@
|
|||||||
from celery import Celery,app
|
""" Background tasks done by Celery (develop) or QR (production) """
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import gc
|
import gc
|
||||||
@@ -7,40 +7,44 @@ import shutil
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import rowingdata
|
import rowingdata
|
||||||
from rowingdata import main as rmain
|
|
||||||
from rowingdata import rowingdata as rdata
|
from rowingdata import rowingdata as rdata
|
||||||
import rowingdata
|
|
||||||
from async_messages import message_user,messages
|
from celery import app
|
||||||
|
|
||||||
from matplotlib.backends.backend_agg import FigureCanvas
|
from matplotlib.backends.backend_agg import FigureCanvas
|
||||||
#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
|
#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from matplotlib import figure
|
|
||||||
|
|
||||||
import stravalib
|
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from django_rq import job
|
from django_rq import job
|
||||||
|
|
||||||
from utils import serialize_list,deserialize_list
|
from utils import deserialize_list
|
||||||
|
|
||||||
from rowers.dataprepnodjango import (
|
from rowers.dataprepnodjango import (
|
||||||
update_strokedata, new_workout_from_file,
|
update_strokedata, new_workout_from_file,
|
||||||
getsmallrowdata_db,read_df_sql,columndict,
|
getsmallrowdata_db,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.core.mail import send_mail, BadHeaderError,EmailMessage
|
from django.core.mail import send_mail, EmailMessage
|
||||||
|
|
||||||
|
|
||||||
import datautils
|
import datautils
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
# testing task
|
# testing task
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def add(x, y):
|
def add(x, y):
|
||||||
return x + y
|
return x + y
|
||||||
|
|
||||||
# create workout
|
# create workout
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_new_workout_from_file(r, f2,
|
def handle_new_workout_from_file(r, f2,
|
||||||
workouttype='rower',
|
workouttype='rower',
|
||||||
@@ -51,6 +55,8 @@ def handle_new_workout_from_file(r,f2,
|
|||||||
title, makeprivate, notes)
|
title, makeprivate, notes)
|
||||||
|
|
||||||
# process and update workouts
|
# process and update workouts
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_updatedps(useremail, workoutids, debug=False):
|
def handle_updatedps(useremail, workoutids, debug=False):
|
||||||
for wid, f1 in workoutids:
|
for wid, f1 in workoutids:
|
||||||
@@ -81,6 +87,8 @@ def handle_updatedps(useremail,workoutids,debug=False):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
# send email when a breakthrough workout is uploaded
|
# send email when a breakthrough workout is uploaded
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_breakthrough(workoutid, useremail,
|
def handle_sendemail_breakthrough(workoutid, useremail,
|
||||||
userfirstname, userlastname,
|
userfirstname, userlastname,
|
||||||
@@ -124,7 +132,6 @@ def handle_sendemail_breakthrough(workoutid,useremail,
|
|||||||
pwr=pwr
|
pwr=pwr
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n"
|
message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n"
|
||||||
|
|
||||||
message += "Best Regards, the Rowsandall Team"
|
message += "Best Regards, the Rowsandall Team"
|
||||||
@@ -133,13 +140,14 @@ def handle_sendemail_breakthrough(workoutid,useremail,
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[useremail])
|
[useremail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
# remove tcx file
|
# remove tcx file
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# send email when a breakthrough workout is uploaded
|
# send email when a breakthrough workout is uploaded
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_hard(workoutid, useremail,
|
def handle_sendemail_hard(workoutid, useremail,
|
||||||
userfirstname, userlastname,
|
userfirstname, userlastname,
|
||||||
@@ -167,7 +175,6 @@ def handle_sendemail_hard(workoutid,useremail,
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[useremail])
|
[useremail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
# remove tcx file
|
# remove tcx file
|
||||||
@@ -206,6 +213,37 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
# send email to owner when an unrecognized file is uploaded
|
||||||
|
@app.task
|
||||||
|
def handle_sendemail_unrecognizedowner(useremail, userfirstname):
|
||||||
|
|
||||||
|
# send email with attachment
|
||||||
|
fullemail = useremail
|
||||||
|
subject = "Unrecognized file from Rowsandall.com"
|
||||||
|
message = "Dear " + userfirstname + ",\n\n"
|
||||||
|
message += """
|
||||||
|
The file you tried to send to rowsandall.com was not recognized by
|
||||||
|
our email processing system. You may have sent a file in a format
|
||||||
|
that is not supported. Sometimes, rowing apps make file format changes.
|
||||||
|
When that happens, it takes some time for rowsandall.comm to make
|
||||||
|
the necessary changes on our side and support the app again.
|
||||||
|
|
||||||
|
The file has been sent to the developer at rowsandall.com for evaluation.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
message += "Best Regards, the Rowsandall Team"
|
||||||
|
|
||||||
|
email = EmailMessage(subject, message,
|
||||||
|
'Rowsandall <info@rowsandall.com>',
|
||||||
|
[fullemail])
|
||||||
|
|
||||||
|
res = email.send()
|
||||||
|
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
# Send email with TCX attachment
|
# Send email with TCX attachment
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemailtcx(first_name, last_name, email, tcxfile):
|
def handle_sendemailtcx(first_name, last_name, email, tcxfile):
|
||||||
@@ -221,7 +259,6 @@ def handle_sendemailtcx(first_name,last_name,email,tcxfile):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
email.attach_file(tcxfile)
|
email.attach_file(tcxfile)
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
@@ -230,6 +267,7 @@ def handle_sendemailtcx(first_name,last_name,email,tcxfile):
|
|||||||
os.remove(tcxfile)
|
os.remove(tcxfile)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_zip_file(emailfrom, subject, file):
|
def handle_zip_file(emailfrom, subject, file):
|
||||||
message = "... zip processing ... "
|
message = "... zip processing ... "
|
||||||
@@ -242,6 +280,8 @@ def handle_zip_file(emailfrom,subject,file):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Send email with CSV attachment
|
# Send email with CSV attachment
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemailcsv(first_name, last_name, email, csvfile):
|
def handle_sendemailcsv(first_name, last_name, email, csvfile):
|
||||||
|
|
||||||
@@ -256,7 +296,6 @@ def handle_sendemailcsv(first_name,last_name,email,csvfile):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
if os.path.isfile(csvfile):
|
if os.path.isfile(csvfile):
|
||||||
email.attach_file(csvfile)
|
email.attach_file(csvfile)
|
||||||
else:
|
else:
|
||||||
@@ -272,9 +311,12 @@ def handle_sendemailcsv(first_name,last_name,email,csvfile):
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Calculate wind and stream corrections for OTW rowing
|
# Calculate wind and stream corrections for OTW rowing
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_otwsetpower(f1, boattype, weightvalue,
|
def handle_otwsetpower(f1, boattype, weightvalue,
|
||||||
first_name,last_name,email,workoutid,ps=[1,1,1,1],
|
first_name, last_name, email, workoutid, ps=[
|
||||||
|
1, 1, 1, 1],
|
||||||
ratio=1.0,
|
ratio=1.0,
|
||||||
debug=False):
|
debug=False):
|
||||||
try:
|
try:
|
||||||
@@ -317,7 +359,8 @@ def handle_otwsetpower(f1,boattype,weightvalue,
|
|||||||
rowdata.write_csv(f1, gzip=True)
|
rowdata.write_csv(f1, gzip=True)
|
||||||
update_strokedata(workoutid, rowdata.df, debug=debug)
|
update_strokedata(workoutid, rowdata.df, debug=debug)
|
||||||
|
|
||||||
totaltime = rowdata.df['TimeStamp (sec)'].max()-rowdata.df['TimeStamp (sec)'].min()
|
totaltime = rowdata.df['TimeStamp (sec)'].max(
|
||||||
|
) - rowdata.df['TimeStamp (sec)'].min()
|
||||||
try:
|
try:
|
||||||
totaltime = totaltime + rowdata.df.ix[0, ' ElapsedTime (sec)']
|
totaltime = totaltime + rowdata.df.ix[0, ' ElapsedTime (sec)']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -329,9 +372,9 @@ def handle_otwsetpower(f1,boattype,weightvalue,
|
|||||||
dfgrouped = df.groupby(['workoutid'])
|
dfgrouped = df.groupby(['workoutid'])
|
||||||
delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr)
|
delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr)
|
||||||
|
|
||||||
|
|
||||||
#delta,cpvalues,avgpower = datautils.getsinglecp(rowdata.df)
|
#delta,cpvalues,avgpower = datautils.getsinglecp(rowdata.df)
|
||||||
res,btvalues,res2 = utils.isbreakthrough(delta,cpvalues,ps[0],ps[1],ps[2],ps[3],ratio)
|
res, btvalues, res2 = utils.isbreakthrough(
|
||||||
|
delta, cpvalues, ps[0], ps[1], ps[2], ps[3], ratio)
|
||||||
if res:
|
if res:
|
||||||
handle_sendemail_breakthrough(workoutid, email,
|
handle_sendemail_breakthrough(workoutid, email,
|
||||||
first_name,
|
first_name,
|
||||||
@@ -358,6 +401,8 @@ def handle_otwsetpower(f1,boattype,weightvalue,
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
# This function generates all the static (PNG image) plots
|
# This function generates all the static (PNG image) plots
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename):
|
def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename):
|
||||||
|
|
||||||
@@ -381,7 +426,6 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
|
|||||||
except IOError:
|
except IOError:
|
||||||
row = rdata(f2 + '.gz', rower=rr)
|
row = rdata(f2 + '.gz', rower=rr)
|
||||||
|
|
||||||
|
|
||||||
haspower = row.df[' Power (watts)'].mean() > 50
|
haspower = row.df[' Power (watts)'].mean() > 50
|
||||||
|
|
||||||
nr_rows = len(row.df)
|
nr_rows = len(row.df)
|
||||||
@@ -430,6 +474,7 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
|
|||||||
|
|
||||||
# Team related remote tasks
|
# Team related remote tasks
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_invite(email, name, code, teamname, manager):
|
def handle_sendemail_invite(email, name, code, teamname, manager):
|
||||||
fullemail = name + ' <' + email + '>'
|
fullemail = name + ' <' + email + '>'
|
||||||
@@ -461,11 +506,11 @@ def handle_sendemail_invite(email,name,code,teamname,manager):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemailnewresponse(first_name, last_name,
|
def handle_sendemailnewresponse(first_name, last_name,
|
||||||
email,
|
email,
|
||||||
@@ -482,21 +527,23 @@ def handle_sendemailnewresponse(first_name,last_name,
|
|||||||
message += comment
|
message += comment
|
||||||
message += '\n\n'
|
message += '\n\n'
|
||||||
message += 'You can read the comment here:\n'
|
message += 'You can read the comment here:\n'
|
||||||
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment'
|
message += 'https://rowsandall.com/rowers/workout/' + \
|
||||||
|
str(workoutid) + '/comment'
|
||||||
message += '\n\n'
|
message += '\n\n'
|
||||||
message += 'You are receiving this email because you are subscribed '
|
message += 'You are receiving this email because you are subscribed '
|
||||||
message += 'to comments on this workout. To unsubscribe, follow this link:\n'
|
message += 'to comments on this workout. To unsubscribe, follow this link:\n'
|
||||||
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/unsubscribe'
|
message += 'https://rowsandall.com/rowers/workout/' + \
|
||||||
|
str(workoutid) + '/unsubscribe'
|
||||||
|
|
||||||
email = EmailMessage(subject, message,
|
email = EmailMessage(subject, message,
|
||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemailnewcomment(first_name,
|
def handle_sendemailnewcomment(first_name,
|
||||||
last_name,
|
last_name,
|
||||||
@@ -514,13 +561,13 @@ def handle_sendemailnewcomment(first_name,
|
|||||||
message += comment
|
message += comment
|
||||||
message += '\n\n'
|
message += '\n\n'
|
||||||
message += 'You can read the comment here:\n'
|
message += 'You can read the comment here:\n'
|
||||||
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment'
|
message += 'https://rowsandall.com/rowers/workout/' + \
|
||||||
|
str(workoutid) + '/comment'
|
||||||
|
|
||||||
email = EmailMessage(subject, message,
|
email = EmailMessage(subject, message,
|
||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
@@ -545,11 +592,11 @@ def handle_sendemail_request(email,name,code,teamname,requestor,id):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_request_accept(email, name, teamname, managername):
|
def handle_sendemail_request_accept(email, name, teamname, managername):
|
||||||
fullemail = name + ' <' + email + '>'
|
fullemail = name + ' <' + email + '>'
|
||||||
@@ -565,11 +612,11 @@ def handle_sendemail_request_accept(email,name,teamname,managername):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_request_reject(email, name, teamname, managername):
|
def handle_sendemail_request_reject(email, name, teamname, managername):
|
||||||
fullemail = name + ' <' + email + '>'
|
fullemail = name + ' <' + email + '>'
|
||||||
@@ -586,11 +633,11 @@ def handle_sendemail_request_reject(email,name,teamname,managername):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_member_dropped(email, name, teamname, managername):
|
def handle_sendemail_member_dropped(email, name, teamname, managername):
|
||||||
fullemail = name + ' <' + email + '>'
|
fullemail = name + ' <' + email + '>'
|
||||||
@@ -607,11 +654,11 @@ def handle_sendemail_member_dropped(email,name,teamname,managername):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_team_removed(email, name, teamname, managername):
|
def handle_sendemail_team_removed(email, name, teamname, managername):
|
||||||
fullemail = name + ' <' + email + '>'
|
fullemail = name + ' <' + email + '>'
|
||||||
@@ -629,11 +676,11 @@ def handle_sendemail_team_removed(email,name,teamname,managername):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_invite_reject(email, name, teamname, managername):
|
def handle_sendemail_invite_reject(email, name, teamname, managername):
|
||||||
fullemail = managername + ' <' + email + '>'
|
fullemail = managername + ' <' + email + '>'
|
||||||
@@ -650,11 +697,11 @@ def handle_sendemail_invite_reject(email,name,teamname,managername):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
@app.task
|
@app.task
|
||||||
def handle_sendemail_invite_accept(email, name, teamname, managername):
|
def handle_sendemail_invite_accept(email, name, teamname, managername):
|
||||||
fullemail = managername + ' <' + email + '>'
|
fullemail = managername + ' <' + email + '>'
|
||||||
@@ -667,13 +714,11 @@ def handle_sendemail_invite_accept(email,name,teamname,managername):
|
|||||||
'Rowsandall <info@rowsandall.com>',
|
'Rowsandall <info@rowsandall.com>',
|
||||||
[fullemail])
|
[fullemail])
|
||||||
|
|
||||||
|
|
||||||
res = email.send()
|
res = email.send()
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Another simple task for debugging purposes
|
# Another simple task for debugging purposes
|
||||||
def add2(x, y):
|
def add2(x, y):
|
||||||
return x + y
|
return x + y
|
||||||
|
|||||||
Reference in New Issue
Block a user