Рубрики
Без рубрики

WXPYMAIL: Создание приложения для отправки электронных писем

Получите практические, реальные навыки Python на наших ресурсах и пути

Автор оригинала: Mike Driscoll.

Я думал, что было бы хорошей идеей, чтобы написать приложение образца в Wxpython, чтобы показать, как поставить все куски вместе и сделать что-то полезное. На моей дневной работе я создал небольшую программу для отправки электронных писем, потому что у нас было много пользователей, которые пропустили функциональность Mailto, которую мы проиграли, когда мы переключились с Exchange/Outlook в Зимбру. Следует отметить, что это только приложение Windows только в настоящее время, но не должно быть слишком сложно, чтобы сделать его больше ОС-Агностики.

Я разделим эту статью на три части: сначала создает интерфейс; Во-вторых устанавливает обработку данных, и в третьем будет создание исполняемого окна и подключение к обработчику MailTo. Когда мы закончим, графический интерфейс будет выглядеть что-то подобное:

Чтобы следовать, вам понадобится следующее:

Создание интерфейса

Давайте перейдем по коду ниже. Как видите, я основываю это приложение на объекте wx.frame и экземпляром wx.pysimpleapp, чтобы сделать приложение.

import os
import sys
import urllib
import wx

import mail_ico

class SendMailWx(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'New Email Message (Plain Text)',
                          size=(600,400))
        self.panel = wx.Panel(self, wx.ID_ANY)

        # set your email address here
        self.email = 'myEmail@email.com'

        self.filepaths = []        
        self.currentDir = os.path.abspath(os.path.dirname(sys.argv[0])) 
        
        self.createMenu()
        self.createToolbar()
        self.createWidgets()
        try:
            print sys.argv
            self.parseURL(sys.argv[1])
        except Exception, e:
            print 'Unable to execute parseURL...'
            print e
        
        self.layoutWidgets()
        
        self.attachTxt.Hide()
        self.editAttachBtn.Hide()

    def createMenu(self):
        menubar = wx.MenuBar()
        
        fileMenu = wx.Menu()
        send_menu_item = fileMenu.Append(wx.NewId(), '&Send', 'Sends the email')
        close_menu_item = fileMenu.Append(wx.NewId(), '&Close', 'Closes the window')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        # bind events to the menu items
        self.Bind(wx.EVT_MENU, self.onSend, send_menu_item)
        self.Bind(wx.EVT_MENU, self.onClose, close_menu_item)

    def createToolbar(self):
        toolbar = self.CreateToolBar(wx.TB_3DBUTTONS|wx.TB_TEXT)
        toolbar.SetToolBitmapSize((31,31))
        bmp = mail_ico.getBitmap()
        sendTool = toolbar.AddSimpleTool(-1, bmp, 'Send', 'Sends Email')
        self.Bind(wx.EVT_MENU, self.onSend, sendTool)        
        toolbar.Realize()

    def createWidgets(self):
        p = self.panel
        
        font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.BOLD)
        self.fromLbl    = wx.StaticText(p, wx.ID_ANY, 'From', size=(60,-1))
        self.fromTxt    = wx.TextCtrl(p, wx.ID_ANY, self.email)
        self.toLbl      = wx.StaticText(p, wx.ID_ANY, 'To:', size=(60,-1))
        self.toTxt      = wx.TextCtrl(p, wx.ID_ANY, '')
        self.subjectLbl = wx.StaticText(p, wx.ID_ANY, ' Subject:', size=(60,-1))
        self.subjectTxt = wx.TextCtrl(p, wx.ID_ANY, '')
        self.attachBtn  = wx.Button(p, wx.ID_ANY, 'Attachments')        
        self.attachTxt  = wx.TextCtrl(p, wx.ID_ANY, '', style=wx.TE_MULTILINE)
        self.attachTxt.Disable()
        self.editAttachBtn = wx.Button(p, wx.ID_ANY, 'Edit Attachments')
        
        self.messageTxt = wx.TextCtrl(p, wx.ID_ANY, '', style=wx.TE_MULTILINE)

        self.Bind(wx.EVT_BUTTON, self.onAttach, self.attachBtn)
        self.Bind(wx.EVT_BUTTON, self.onAttachEdit, self.editAttachBtn)

        self.fromLbl.SetFont(font)
        self.toLbl.SetFont(font)
        self.subjectLbl.SetFont(font)

    def layoutWidgets(self):
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        fromSizer = wx.BoxSizer(wx.HORIZONTAL)
        toSizer   = wx.BoxSizer(wx.HORIZONTAL)
        subjSizer = wx.BoxSizer(wx.HORIZONTAL)
        attachSizer = wx.BoxSizer(wx.HORIZONTAL)

        fromSizer.Add(self.fromLbl, 0)
        fromSizer.Add(self.fromTxt, 1, wx.EXPAND)
        toSizer.Add(self.toLbl, 0)
        toSizer.Add(self.toTxt, 1, wx.EXPAND)
        subjSizer.Add(self.subjectLbl, 0)
        subjSizer.Add(self.subjectTxt, 1, wx.EXPAND)
        attachSizer.Add(self.attachBtn, 0, wx.ALL, 5)
        attachSizer.Add(self.attachTxt, 1, wx.ALL|wx.EXPAND, 5)
        attachSizer.Add(self.editAttachBtn, 0, wx.ALL, 5)

        mainSizer.Add(fromSizer, 0, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(toSizer, 0, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(subjSizer, 0, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(attachSizer, 0, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(self.messageTxt, 1, wx.ALL|wx.EXPAND, 5)        
        self.panel.SetSizer(mainSizer)
        self.panel.Layout()

    def parseURL(self, url):
        ''' Parse the URL passed from the mailto link '''
        sections = 1
        mailto_string = url.split(':')[1]               
        
        if '?' in mailto_string:
            sections = mailto_string.split('?')
        else:
            address = mailto_string
            
        if len(sections) > 1:
            address = sections[0]
            new_sections = urllib.unquote(sections[1]).split('&')
            for item in new_sections:
                if 'subject' in item.lower():
                    Subject = item.split('=')[1]
                    self.subjectTxt.SetValue(Subject)
                if 'body' in item.lower():
                    Body = item.split('=')[1]
                    self.messageTxt.SetValue(Body)
                    
        self.toTxt.SetValue(address)

    def onAttach(self, event):
        '''
        Displays a File Dialog to allow the user to choose a file
        and then attach it to the email.
        ''' 
        print "in onAttach method..."

    def onAttachEdit(self, event):
        ''' Allow the editing of the attached files list '''
        print "in onAttachEdit method..."

    def onSend(self, event):
        ''' Send the email using the filled out textboxes.
            Warn the user if they forget to fill part
            of it out.
        '''
        print "in onSend event handler..."

    def onClose(self, event):
        self.Close()
                    
#######################
# Start program
if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = SendMailWx()
    frame.Show()
    app.MainLoop() 

Я уже объяснил, как создавать панели инструментов, меню и сизеров на предыдущих постах, поэтому я собираюсь сосредоточиться на новом магазине здесь. Я импортирую модуль Urllib, чтобы помочь в расстановке данных, отправленных из ссылки MailTo на веб-странице. В настоящее время я поддерживаю поля To, субъект и телосложение протокола Mailto. Соответствующие текстовые ящики устанавливаются в зависимости от количества разделов, которые передаются в метод ParseURL (). Вы могли легко продлить это нужно. Я также получаю каталог, в котором сценарий бежит с помощью этой строки кода:

self.currentDir = os.path.abspath(os.path.dirname(sys.argv[0]))

Наконец, есть три охранника событий: «Онаттач», «ОНАТАХЕДИТИТЬ» и «Onsend». Давайте пойдем вперед и немного немного.

Прикрепление электронной почты

Первый метод, Onattach (), позволяет пользователю прикреплять файлы к их электронной почте. Я использую WX.FileDialog, чтобы получить выбор пользователя. Вот где находится собственность «FilePaths». Я также называю новый метод, GetFileSize, который рассчитает размер файла. Смотрите код ниже:

def onAttach(self, event):
    '''
    Displays a File Dialog to allow the user to choose a file
    and then attach it to the email.
    '''        
    attachments = self.attachTxt.GetLabel()
    filepath = ''

    # create a file dialog
    wildcard = "All files (*.*)|*.*"
    dialog = wx.FileDialog(None, 'Choose a file', self.currentDir,
                           '', wildcard, wx.OPEN)
    # if the user presses OK, get the path
    if dialog.ShowModal() == wx.ID_OK:
        self.attachTxt.Show()
        self.editAttachBtn.Show()
        filepath = dialog.GetPath()
        print filepath
        # Change the current directory to reflect the last dir opened
        os.chdir(os.path.dirname(filepath))
        self.currentDir = os.getcwd()   

        # add the user's file to the filepath list
        if filepath != '':
            self.filepaths.append(filepath)

        # get file size
        fSize = self.getFileSize(filepath)
        
        # modify the attachment's label based on it's current contents
        if attachments == '':
            attachments = '%s (%s)' % (os.path.basename(filepath), fSize)
        else:
            temp = '%s (%s)' % (os.path.basename(filepath), fSize)
            attachments = attachments + '; ' + temp
        self.attachTxt.SetLabel(attachments)
    dialog.Destroy()
    
def getFileSize(self, f):
    ''' Get the file's approx. size '''
    fSize = os.stat(f).st_size
    if fSize >= 1073741824: # gigabyte
        fSize = int(math.ceil(fSize/1073741824.0))
        size = '%s GB' % fSize
    elif fSize >= 1048576:  # megabyte
        fSize = int(math.ceil(fSize/1048576.0))
        size = '%s MB' % fSize
    elif fSize >= 1024:           # kilobyte
        fSize = int(math.ceil(fSize/1024.0))
        size = '%s KB' % fSize
    else:
        size = '%s bytes' % fSize
    return size

Вы также заметите, что сохраняете последний каталог, который входит в пользователь. Я все еще сталкиваюсь с программами, которые не делают этого или не делают это последовательно. Надеюсь, моя реализация будет работать в большинстве случаев. Предполагается, что метод GetFileize () должен рассчитать размер прикрепленного файла. Это только отображает ближайший размер и не показывает фракции. Кроме того, я думаю, что это красиво самоуверенно.

Редактирование ваших вложений

Метод ONATTACHEDIT () довольно похож, за исключением того, что он вызывает пользовательский диалог, позволяющий пользователю редактировать, какие файлы включены в случай, если они выбрали один ошибочно.

def onAttachEdit(self, event):
    ''' Allow the editing of the attached files list '''
    print 'in onAttachEdit...'
    attachments = ''
    
    dialog = EditDialog(self.filepaths)
    dialog.ShowModal()
    self.filepaths = dialog.filepaths
    print 'Edited paths:\n', self.filepaths
    dialog.Destroy()

    if self.filepaths == []:
        # hide the attachment controls
        self.attachTxt.Hide()
        self.editAttachBtn.Hide()
    else:
        for path in self.filepaths:
            # get file size
            fSize = self.getFileSize(path)
            # Edit the attachments listed
            if attachments == '':
                attachments = '%s (%s)' % (os.path.basename(path), fSize)
            else:
                temp = '%s (%s)' % (os.path.basename(path), fSize)
                attachments = attachments + '; ' + temp            

        self.attachTxt.SetLabel(attachments)

        class EditDialog(wx.Dialog):

    def __init__(self, filepaths):
        wx.Dialog.__init__(self, None, -1, 'Edit Attachments', size=(190,150))

        self.filepaths = filepaths
        
        instructions = 'Check the items below that you no longer wish to attach to the email'
        lbl = wx.StaticText(self, wx.ID_ANY, instructions)
        deleteBtn = wx.Button(self, wx.ID_ANY, 'Delete Items')
        cancelBtn = wx.Button(self, wx.ID_ANY, 'Cancel')

        self.Bind(wx.EVT_BUTTON, self.onDelete, deleteBtn)
        self.Bind(wx.EVT_BUTTON, self.onCancel, cancelBtn)
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        mainSizer.Add(lbl, 0, wx.ALL, 5)       
        
        self.chkList = wx.CheckListBox(self, wx.ID_ANY, choices=self.filepaths)
        mainSizer.Add(self.chkList, 0, wx.ALL, 5)

        btnSizer.Add(deleteBtn, 0, wx.ALL|wx.CENTER, 5)
        btnSizer.Add(cancelBtn, 0, wx.ALL|wx.CENTER, 5)
        mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)
       
        self.SetSizer(mainSizer)
        self.Fit()
        self.Layout()

    def onCancel(self, event):
        self.Close()

    def onDelete(self, event):
        print 'in onDelete'
        numberOfPaths = len(self.filepaths)
        for item in range(numberOfPaths):            
            val = self.chkList.IsChecked(item)
            if val == True:
                path = self.chkList.GetString(item)
                print path
                for i in range(len(self.filepaths)-1,-1,-1):
                    if path in self.filepaths[i]:
                        del self.filepaths[i]
        print 'new list => ', self.filepaths
        self.Close()

Главное, чтобы заметить в коде выше, состоит в том, что редакторDialog является подклассом WX.Dialog. Причина, по которой я выбрал это через WX.Frame, потому что я хотел, чтобы мой диалог был не модальным, и я думаю, что используя класс WX.Dialog имеет наибольшее значение для этого. Вероятно, самая интересная часть этого класса – мой метод OnDelete, в котором я петлю по пути назад. Я делаю это, поэтому я могу удалить элементы в любом порядке, не содержащий целостность списка. Например, если я неоднократно удалил элемент 2, я бы, вероятно, в конечном итоге удаляю элемент, который я не хотел.

Отправка электронной почты

Мой последний метод – Onsend () один. Я думаю, что это, вероятно, самый сложный, и тот, который потребуется рефакторинг больше всего. В этой реализации все SMTP-элементы жестко закодированы. Давайте посмотрим и посмотрим, как это работает:

def OnSend(self, event):
    ''' Send the email using the filled out textboxes.
        Warn the user if they forget to fill part
        of it out.
    '''
            
    From = self.fromTxt.GetValue()
    To = self.toTxt.GetValue()
    Subject = self.subjectTxt.GetValue()
    text = self.messageTxt.GetValue()

    colon = To.find(';')
    period = To.find(',')
    if colon != -1:
        temp = To.split(';')
        To = self.sendStrip(temp) #';'.join(temp)
    elif period != -1:
        temp = To.split(',')
        To = self.sendStrip(temp) #';'.join(temp)
    else:
        pass

    if To == '':
        print 'add an address to the "To" field!'
        dlg = wx.MessageDialog(None, 'Please add an address to the "To" field and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION)
        dlg.ShowModal()
        dlg.Destroy()  
    elif Subject == '':
        dlg = wx.MessageDialog(None, 'Please add a "Subject" and try again', 'Error', wx.OK|wx.ICON_EXCLAMATION)
        dlg.ShowModal()
        dlg.Destroy()
    elif From == '':
        lg = wx.MessageDialog(None, 'Please add an address to the "From" field and try again',
                              'Error', wx.OK|wx.ICON_EXCLAMATION)
        dlg.ShowModal()
        dlg.Destroy()  
    else:            
        msg = MIMEMultipart()
        msg['From']    = From
        msg['To']      = To
        msg['Subject'] = Subject
        msg['Date']    = formatdate(localtime=True)
        msg.attach( MIMEText(text) )

        if self.filepaths != []:
            print 'attaching file(s)...'
            for path in self.filepaths:
                part = MIMEBase('application', "octet-stream")
                part.set_payload( open(path,"rb").read() )
                Encoders.encode_base64(part)
                part.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(path))
                msg.attach(part)
        
        # edit this to match your mail server (i.e. mail.myserver.com)
        server = smtplib.SMTP('mail.myserver.org')

        # open login dialog
        dlg = LoginDlg(server)
        res = dlg.ShowModal()
        if dlg.loggedIn:
            dlg.Destroy()   # destroy the dialog
            try:
                failed = server.sendmail(From, To, msg.as_string())
                server.quit()                    
                self.Close()    # close the program
            except Exception, e:
                print 'Error - send failed!'
                print e
            else:
                if failed: print 'Failed:', failed 
         else:
            dlg.Destroy()                

Большинство из этого вы видели раньше, поэтому я собираюсь поговорить о звонках модуля электронной почты. Tha Основная часть ключей в том, что для создания сообщения электронной почты с вложениями вы захотите использовать mimemultiPart czll. Я использовал его, чтобы добавить поля «из», «на», «тему» и «дату». Чтобы прикрепить файлы, вам нужно использовать MimeBase. Наконец, отправлять электронное письмо, вам нужно будет установить SMTP-сервер, который я использовал с помощью библиотеки SMPTLIB и вход в систему, именно для которого является логинлг. Я пойду на это следующий, но прежде чем я буду хотеть бы порекомендовать оба соответствующей документации по полной информации, поскольку они намного больше функциональности, которые я не использую в этом примере.

Вход в систему

Я заметил, что мой код не работал за пределами моей организации, и мне потребовалось некоторое время, чтобы выяснить, почему. Оказывается, когда я вошел в работу на работе, я также вошел в нашу систему Webmail, поэтому мне не нужно аутентифицироваться с ним. Когда я снаружи, я делаю. Поскольку это на самом деле довольно нормальная процедура для SMTP-серверов, я включил довольно простой диалог входа в систему. Давайте посмотрим на код:

class LoginDlg(wx.Dialog):

    def __init__(self, server):
        wx.Dialog.__init__(self, None, -1, 'Login', size=(190,150))
        self.server = server
        self.loggedIn = False

        # widgets
        userLbl = wx.StaticText(self, wx.ID_ANY, 'Username:', size=(50, -1))
        self.userTxt = wx.TextCtrl(self, wx.ID_ANY, '')
        passwordLbl = wx.StaticText(self, wx.ID_ANY, 'Password:', size=(50, -1))
        self.passwordTxt = wx.TextCtrl(self, wx.ID_ANY, '', size=(150, -1),
                                       style=wx.TE_PROCESS_ENTER|wx.TE_PASSWORD)
        loginBtn = wx.Button(self, wx.ID_YES, 'Login')
        cancelBtn = wx.Button(self, wx.ID_ANY, 'Cancel')

        self.Bind(wx.EVT_BUTTON, self.OnLogin, loginBtn)
        self.Bind(wx.EVT_TEXT_ENTER, self.OnTextEnter, self.passwordTxt)
        self.Bind(wx.EVT_BUTTON, self.OnClose, cancelBtn)

        # sizer / layout 
        userSizer     = wx.BoxSizer(wx.HORIZONTAL)
        passwordSizer = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer      = wx.BoxSizer(wx.HORIZONTAL)
        mainSizer     = wx.BoxSizer(wx.VERTICAL)

        userSizer.Add(userLbl, 0, wx.ALL, 5)
        userSizer.Add(self.userTxt, 0, wx.ALL, 5)
        passwordSizer.Add(passwordLbl, 0, wx.LEFT|wx.RIGHT, 5)
        passwordSizer.Add(self.passwordTxt, 0, wx.LEFT, 5)
        btnSizer.Add(loginBtn, 0, wx.ALL, 5)
        btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
        mainSizer.Add(userSizer, 0, wx.ALL, 0)
        mainSizer.Add(passwordSizer, 0, wx.ALL, 0)
        mainSizer.Add(btnSizer, 0, wx.ALL|wx.CENTER, 5)

        self.SetSizer(mainSizer)
        self.Fit()
        self.Layout()
        
    def OnTextEnter(self, event):
        ''' When enter is pressed, login method is run. '''
        self.OnLogin('event')

    def OnLogin(self, event):
        '''
        When the "Login" button is pressed, the credentials are authenticated.
        If correct, the email will attempt to be sent. If incorrect, the user
        will be notified.
        '''
        try:
            user = self.userTxt.GetValue()
            pw   = self.passwordTxt.GetValue()
            res = self.server.login(user, pw)
            self.loggedIn = True
            self.OnClose('')
        except:
            message = 'Your username or password is incorrect. Please try again.'
            dlg = wx.MessageDialog(None, message, 'Login Error', wx.OK|wx.ICON_EXCLAMATION)
            dlg.ShowModal()
            dlg.Destroy()

    def OnClose(self, event):
        self.Close()

По большей части мы видели это раньше. Основная часть для замечания состоит в том, что я добавил два стиля к моему паролю TextCtrl: wx.te_process_enter и wx.te_password. Первый позволит вам нажать Введите вхожу в логин, а не на нажатии Вход кнопка явно. Стиль TE_PASSWORD скрывает текст, набранный в TextCtrl с черными кругами или звездочками.

Также вы должны отметить, что ваше имя пользователя может включать URL вашей электронной почты. Например, а не просто Имя пользователя это может быть username@hotmail.com Отказ К счастью, если логин неверный, программа бросит ошибку и отображает диалог, позволяющий пользователю знать.

Взломать реестр

Последняя вещь, которую нужно сделать в Windows, это установить его для использования этого сценария, когда пользователь нажимает на ссылку MailTo. Для этого вам нужно будет связываться с реестром Windows. Прежде чем делать что-нибудь с реестром, обязательно обратитесь к этому, так как всегда есть шанс, что вы можете что-то сломать, включая ОС.

Начать, перейти к началу, запустить и введите Реддит Отказ Теперь перейдите к следующему расположению:

Hkey_classes_root \ mailto \ shell \ open \ command

Просто расширяйте дерево справа, чтобы навигаться по дереву. Один там, вам нужно редактировать ключ (по умолчанию) справа. Это должно быть типа reg_sz. Просто дважды щелкните по нему, чтобы редактировать его. Вот что вы хотите поставить туда:

CMD/C “Set: \ Путь \ to \ python24 && c: \ pathon24 && c: \ path \ to \ python24 \ python.exe c: \ path \ to \ wxpymail.py% 1”

В принципе, это говорит Windows для установки домашнего каталога Python и путь к Python.exe в качестве экологической переменной, которая, я думаю, является только временным. Затем он проходит скрипт wxpymail.py, который мы создали в указанном python.exe. «% 1» – это аргументы, переданные ссылкой Mailto. Как только вы попадаете на кнопку ОК, она сохраняется и просто начать работать.

Обертывание

Теперь вы знаете, как сделать полностью функциональное приложение с WxPython. В моем следующем посте я покажу, как упаковать его как исполняемый файл, чтобы вы могли распространять его.

Некоторые возможные улучшения, которые вы могли бы добавить:

  • Хранить информацию о профиле (то есть ваше имя, электронная почта, подпись и т. Д.)
  • Хранить адреса электронной почты в SQLite
  • Создайте мастер для настройки профиля (ы)
  • Добавить шифрование на электронные письма.

Дальнейшее чтение:

Скачать Источник:

  • WXPYMail Source (.zip)