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

Создание приложения GUI для API NASA с WxPython

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

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

Расти, я всегда нашел вселенную и пространство в целом, чтобы быть захватывающим. Это весело мечтать о том, какие миры остаются неисследованными. Мне также нравится видеть фотографии других миров или думать о просторах пространства. Что это связано с Python, хотя? Что ж, национальное управление по аэронавтике и космическому пространству (NASA) имеет веб-API, который позволяет поискать их библиотеку изображений.

Вы можете прочитать все об этом на их Сайт Отказ

Сайт NASA рекомендует получить Прикладной интерфейс программирования (API) Отказ Если вы перейдете на этот веб-сайт, форма, которую вы заполните, хорошая и короткая.

Технически, вам не нужен ключ API, чтобы сделать запросы против услуг NASA. Однако у них есть ограничение скорости на месте для разработчиков, которые получают доступ к своему сайту без ключа API. Даже с ключом вы ограничены по умолчанию 1000 запросов в час. Если вы перейдете к своему распределению, вы будете временно заблокированы от запросов. Вы можете связаться с NASA, чтобы запросить более высокий предел скорости.

Интересно, что документация на самом деле не говорит, сколько запросов вы можете сделать без ключа API.

Документация API не согласна с документацией API Image API NASA, о которой конечные точки попадают, что делает работу с их сайтом немного запутанным.

Например, вы увидите документацию API, разговаривая об этом URL:

  • https://api.nasa.gov/planetary/apod?api_key=API_KEY_GOES_HERE

Но в документации API изображений корня API:

  • https://images-api.nasa.gov

Для целей этого учебника вы будете использовать последнее.

Эта статья адаптирована из моей книги: создание приложений GUI с покупкой WXPYPHON теперь на LeanPub

Использование API NASA

Когда вы начинаете использовать незнакомые API, всегда лучше начать считать документацию для этого интерфейса. Другой подход будет делать быстрый поиск в Интернете и посмотреть, есть ли пакет Python, который включает ваш целевой API. К сожалению, не существует никаких поддерживаемых библиотек NASA для Python. Когда это произойдет, вы можете создать свой собственный.

Чтобы начать, попробуйте прочитать NASA Изображения Документ API Отказ

Их документация API не очень длинная, поэтому не следует вам очень долго читать или хотя бы снимать.

Следующим шагом является принять эту информацию и попробуйте войти с их API.

Вот первые несколько строк эксперимента при доступе к их API:

# simple_api_request.py

import requests

from urllib.parse import urlencode, quote_plus


base_url = 'https://images-api.nasa.gov/search'
search_term = 'apollo 11'
desc = 'moon landing'
media = 'image'
query = {'q': search_term, 'description': desc, 'media_type': media}
full_url = base_url + '?' + urlencode(query, quote_via=quote_plus)

r = requests.get(full_url)
data = r.json()

Если вы запустите это в отладчике, вы можете распечатать json, который возвращается.

Вот фрагмент того, что было возвращено:

'items': [{'data': 
              [{'center': 'HQ',
                 'date_created': '2009-07-18T00:00:00Z',
                 'description': 'On the eve of the '
                                'fortieth anniversary of '
                                "Apollo 11's first human "
                                'landing on the Moon, '
                                'Apollo 11 crew member, '
                                'Buzz Aldrin speaks during '
                                'a lecture in honor of '
                                'Apollo 11 at the National '
                                'Air and Space Museum in '
                                'Washington, Sunday, July '
                                '19, 2009. Guest speakers '
                                'included Former NASA '
                                'Astronaut and U.S. '
                                'Senator John Glenn, NASA '
                                'Mission Control creator '
                                'and former NASA Johnson '
                                'Space Center director '
                                'Chris Kraft and the crew '
                                'of Apollo 11.  Photo '
                                'Credit: (NASA/Bill '
                                'Ingalls)',
                 'keywords': ['Apollo 11',
                              'Apollo 40th Anniversary',
                              'Buzz Aldrin',
                              'National Air and Space '
                              'Museum (NASM)',
                              'Washington, DC'],
                 'location': 'National Air and Space '
                             'Museum',
                 'media_type': 'image',
                 'nasa_id': '200907190008HQ',
                 'photographer': 'NASA/Bill Ingalls',
                 'title': 'Glenn Lecture With Crew of '
                          'Apollo 11'}],
       'href': 'https://images-assets.nasa.gov/image/200907190008HQ/collection.json',
       'links': [{'href': 'https://images-assets.nasa.gov/image/200907190008HQ/200907190008HQ~thumb.jpg',
                  'rel': 'preview',
                  'render': 'image'}]}

Теперь, когда вы знаете, какой формат JSON, вы можете попробовать его немного.

Давайте добавим следующие строки кода в ваш сценарий Python:

item = data['collection']['items'][0]
nasa_id = item['data'][0]['nasa_id']
asset_url = 'https://images-api.nasa.gov/asset/' + nasa_id
image_request = requests.get(asset_url)
image_json = image_request.json()
image_urls = [url['href'] for url in image_json['collection']['items']]
print(image_urls)

Это извлечет первый элемент в списке Предметы от ответа JSON. Тогда вы можете извлечь nasa_id , который требуется получить все изображения, связанные с этим конкретным результатом. Теперь вы можете добавить это nasa_id на новую конечную точку URL и сделать новый запрос.

Запрос на изображение JSON возвращает это:

{'collection': {'href': 'https://images-api.nasa.gov/asset/200907190008HQ',
                'items': [{'href': 'http://images-assets.nasa.gov/image/200907190008HQ/200907190008HQ~orig.tif'},
                          {'href': 'http://images-assets.nasa.gov/image/200907190008HQ/200907190008HQ~large.jpg'},
                          {'href': 'http://images-assets.nasa.gov/image/200907190008HQ/200907190008HQ~medium.jpg'},
                          {'href': 'http://images-assets.nasa.gov/image/200907190008HQ/200907190008HQ~small.jpg'},
                          {'href': 'http://images-assets.nasa.gov/image/200907190008HQ/200907190008HQ~thumb.jpg'},
                          {'href': 'http://images-assets.nasa.gov/image/200907190008HQ/metadata.json'}],
                'version': '1.0'}}

Последние две строки в вашем Python Code будут извлекать URL-адреса от JSON. Теперь у вас есть все куски, которые вам нужно для написания базового пользовательского интерфейса!

Проектирование пользовательского интерфейса

Существует много разных способов разработать ваше приложение для загрузки изображения. Вы будете делать то, что простые, так как это почти всегда самый быстрый способ создать прототип. Приятная вещь о прототипов заключается в том, что вы окажетесь со всеми кусками, которые вам нужно будет создать полезное приложение. Тогда вы можете предпринять свои знания и либо улучшить прототип, либо создать что-то новое со знаниями, которые вы получили.

Вот макет того, что вы будете пытаться создать:

Как видите, вы захотите приложение со следующими функциями:

  • Поисковая строка
  • Виджет для проведения результатов поиска
  • Способ отобразить изображение, когда результат выбран
  • Возможность скачать изображение

Давайте узнаем, как создать этот пользовательский интерфейс сейчас!

Создание приложения поиска NASA

Быстрый прототип – это идея, в которой вы будете создавать небольшое, родильное приложение как можно быстрее. Вместо того, чтобы проводить много времени, чтобы все виджеты выложили, давайте добавим их сверху вниз в приложении. Это даст вам что-то работать с быстрее, чем создание серии вложенных суставов.

Давайте начнем с создания сценария под названием nasa_search_ui.py :

# nasa_search_ui.py

import os
import requests
import wx

from download_dialog import DownloadDialog
from ObjectListView import ObjectListView, ColumnDefn
from urllib.parse import urlencode, quote_plus

Здесь вы импортируете несколько новых предметов, которые вы еще не видели. Первый – это Запросы упаковка. Это удобный пакет для загрузки файлов и делать вещи в Интернете с Python. Многие разработчики считают, что это лучше, чем собственный Урлиб Питона. Вам нужно будет установить его, чтобы использовать его, хотя. Вам также потребуется Instal ObjectListView.

Вот как вы можете сделать это с Пип :

pip install requests ObjectListView

Другой кусок, который новый – это импорт из Urllib.Parse Отказ Вы будете использовать этот модуль для кодирования параметров URL. Наконец, DownloadDialog Это класс для небольшого диалога, который вы будете создавать для загрузки изображений NASA.

Поскольку вы будете использовать ObjectListView в этом приложении, вам понадобится класс для представления объектов в этом виджете:

class Result:

    def __init__(self, item):
        data = item['data'][0]
        self.title = data['title']
        self.location = data.get('location', '')
        self.nasa_id = data['nasa_id']
        self.description = data['description']
        self.photographer = data.get('photographer', '')
        self.date_created = data['date_created']
        self.item = item

        if item.get('links'):
            try:
                self.thumbnail = item['links'][0]['href']
            except:
                self.thumbnail = ''

Результат Класс – это то, что вы будете использовать, чтобы удерживать эти данные, которые составляют каждую строку в вашем ObjectListView Отказ Предмет Параметр – это часть JSON, которую вы получаете от NASA в качестве ответа на ваш запрос. В этом классе вам нужно будет анализировать информацию, которую вам требуется.

В этом случае вы хотите следующие поля:

  • Заголовок
  • Расположение изображения
  • Внутренний идентификатор НАСА
  • Описание фото
  • Имя фотографа
  • Дата, когда изображение было создано
  • URL миниатюр

Некоторые из этих предметов не всегда включены в ответ JSON, поэтому вы будете использовать словарь Получить () Способ вернуть пустую строку в этих случаях.

Теперь начнем работать на пользовательском интерфейсе:

class MainPanel(wx.Panel):

    def __init__(self, parent):
        super().__init__(parent)
        self.search_results = []
        self.max_size = 300
        self.paths = wx.StandardPaths.Get()
        font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL)

        main_sizer = wx.BoxSizer(wx.VERTICAL)

Mainpanel это где основная часть вашего кода будет. Здесь вы делаете немного уборки и создаете search_results держать список Результат Объекты, когда пользователь делает поиск. Вы также устанавливаете max_size Изображение миниатюры, шрифт для использования, Sizer, и вы также получаете несколько стандартных путей.

Теперь давайте добавим следующий код для __init __ () :

txt = 'Search for images on NASA'
label = wx.StaticText(self, label=txt)
main_sizer.Add(label, 0, wx.ALL, 5)
self.search = wx.SearchCtrl(
    self, style=wx.TE_PROCESS_ENTER, size=(-1, 25))
self.search.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, self.on_search)
self.search.Bind(wx.EVT_TEXT_ENTER, self.on_search)
main_sizer.Add(self.search, 0, wx.EXPAND)

Здесь вы создаете ярлык заголовка для приложения, используя wx.statictext Отказ Тогда вы добавляете wx.searchctrl , что очень похоже на wx.textctrl За исключением того, что у него встроены специальные кнопки. Вы также связываете событие кнопки поиска ( evt_searchctrl_search_btn ) и Evt_text_enter на поисковый обработчик событий ( on_search ).

Следующие несколько строк Добавьте виджет результатов поиска:

self.search_results_olv = ObjectListView(
    self, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
self.search_results_olv.SetEmptyListMsg("No Results Found")
self.search_results_olv.Bind(wx.EVT_LIST_ITEM_SELECTED,
                             self.on_selection)
main_sizer.Add(self.search_results_olv, 1, wx.EXPAND)
self.update_search_results()

Этот код устанавливает ObjectListView Также так же, как некоторые из моих других статей, используют его. Вы настраиваете пустое сообщение, позвонив SETPEPTYLISTMSG () И вы также связываете виджет к Evt_list_item_selected Так что вы делаете что-то, когда пользователь выбирает результат поиска.

Теперь давайте добавим остальную часть кода к __init __ () Метод:

main_sizer.AddSpacer(30)
self.title = wx.TextCtrl(self, style=wx.TE_READONLY)
self.title.SetFont(font)
main_sizer.Add(self.title, 0, wx.ALL|wx.EXPAND, 5)
img = wx.Image(240, 240)
self.image_ctrl = wx.StaticBitmap(self,
                                  bitmap=wx.Bitmap(img))
main_sizer.Add(self.image_ctrl, 0, wx.CENTER|wx.ALL, 5
               )
download_btn = wx.Button(self, label='Download Image')
download_btn.Bind(wx.EVT_BUTTON, self.on_download)
main_sizer.Add(download_btn, 0, wx.ALL|wx.CENTER, 5)

self.SetSizer(main_sizer)

Эти последние несколько строк кода добавляют элемент управления заголовком и виджет изображения, который будет обновляться, когда выбран результат. Вы также добавляете кнопку загрузки, чтобы позволить пользователю выбрать, какой размер изображения они хотели бы загрузить. NASA обычно дает несколько разных версий изображения от миниатюр до оригинального изображения TIFF.

Первый обработчик событий, чтобы посмотреть на on_download () :

def on_download(self, event):
    selection = self.search_results_olv.GetSelectedObject()
    if selection:
        with DownloadDialog(selection) as dlg:
            dlg.ShowModal()

Вот вы звоните GetSelecedObject () Чтобы получить выбор пользователя. Если пользователь ничего не выбрал, то этот метод выходит. С другой стороны, если пользователь выбрал элемент, то вы создаете создание DownloadDialog И покажите это пользователю, чтобы позволить им что-то скачать.

Теперь давайте узнаем, как сделать поиск:

def on_search(self, event):
    search_term = event.GetString()
    if search_term:
        query = {'q': search_term, 'media_type': 'image'}
        full_url = base_url + '?' + urlencode(query, quote_via=quote_plus)
        r = requests.get(full_url)
        data = r.json()
        self.search_results = []
        for item in data['collection']['items']:
            if item.get('data') and len(item.get('data')) > 0:
                data = item['data'][0]
                if data['title'].strip() == '':
                    # Skip results with blank titles
                    continue
                result = Result(item)
                self.search_results.append(result)
        self.update_search_results()

on_earch () Обработчик событий получит строку, которую пользователь вводил в управление поиском или возвращать пустую строку. Предполагая, что пользователь на самом деле входит в чем-то поиску, вы используете общий поисковый запрос NASA, Q и жесткий код Media_Type к изображение Отказ Затем вы кодируете запрос в правильно отформатированный URL-адрес и используете requests.get () запросить ответ JSON.

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

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

Прежде чем мы доберемся до этой функции, вам нужно будет создать on_selection () :

def on_selection(self, event):
    selection = self.search_results_olv.GetSelectedObject()
    self.title.SetValue(f'{selection.title}')
    if selection.thumbnail:
        self.update_image(selection.thumbnail)
    else:
        img = wx.Image(240, 240)
        self.image_ctrl.SetBitmap(wx.Bitmap(img))
        self.Refresh()
        self.Layout()

Еще раз, вы получите выбранный элемент, но на этот раз вы принимаете этот выбор и обновляете элемент управления заголовком с текстом заголовка выбора. Затем вы проверяете, есть ли миниатюра и обновить, что соответственно, если есть один. Когда нет миниатюр, вы устанавливаете его обратно в пустое изображение, как вы не хотите, чтобы он продолжал отображать ранее выбранное изображение.

Следующий метод для создания – это update_image () :

def update_image(self, url):
    filename = url.split('/')[-1]
    tmp_location = os.path.join(self.paths.GetTempDir(), filename)
    r = requests.get(url)
    with open(tmp_location, "wb") as thumbnail:
        thumbnail.write(r.content)

    if os.path.exists(tmp_location):
        img = wx.Image(tmp_location, wx.BITMAP_TYPE_ANY)
        W = img.GetWidth()
        H = img.GetHeight()
        if W > H:
            NewW = self.max_size
            NewH = self.max_size * H / W
        else:
            NewH = self.max_size
            NewW = self.max_size * W / H
        img = img.Scale(NewW,NewH)
    else:
        img = wx.Image(240, 240)

    self.image_ctrl.SetBitmap(wx.Bitmap(img))
    self.Refresh()
    self.Layout()

update_image () принимает URL как его единственный аргумент. Требуется этот URL и разбивает Имя файла Отказ Затем он создает новое расположение загрузки, которое является каталогом TEMP компьютера. Затем ваш код загружает изображение и проверяет, чтобы убедиться, что файл сохранен правильно. Если это сделало, то миниатюра загружена с помощью max_size что вы набор; В противном случае вы устанавливаете его для использования пустого изображения.

Последние пару строк Обновить () и Макет () Панель, так что виджет отображается правильно.

Наконец, вам нужно создать последний метод:

def update_search_results(self):
    self.search_results_olv.SetColumns([
        ColumnDefn("Title", "left", 250, "title"),
        ColumnDefn("Description", "left", 350, "description"),
        ColumnDefn("Photographer", "left", 100, "photographer"),
        ColumnDefn("Date Created", "left", 150, "date_created")
    ])
    self.search_results_olv.SetObjects(self.search_results)

Здесь вы создаете кадр, установите заголовок и начальный размер и добавьте панель. Затем вы показываете кадр.

Это то, как будет выглядеть главное UI:

Теперь давайте узнаем, что отправится в создание диалога загрузки!

Диалог загрузки

Диалог загрузки позволит пользователю загружать один или несколько изображений, которые они выбрали. Есть почти всегда, по меньшей мере, две версии каждого изображения, а иногда и пять или шесть.

Первый кусок кода, который нужно узнать о первых нескольких линиях:

# download_dialog.py

import requests
import wx

wildcard = "All files (*.*)|*.*"

Вот вы еще раз импортируете Запросы и настроить подстановочный знак что вы будете использовать при сохранении изображений.

Теперь давайте создадим диалог __init __ () :

class DownloadDialog(wx.Dialog):

    def __init__(self, selection):
        super().__init__(None, title='Download images')
        self.paths = wx.StandardPaths.Get()
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.list_box = wx.ListBox(self, choices=[], size=wx.DefaultSize)
        urls = self.get_image_urls(selection)
        if urls:
            choices = {url.split('/')[-1]: url for url in urls if 'jpg' in url}
            for choice in choices:
                self.list_box.Append(choice, choices[choice])
        main_sizer.Add(self.list_box, 1, wx.EXPAND|wx.ALL, 5)

        save_btn = wx.Button(self, label='Save')
        save_btn.Bind(wx.EVT_BUTTON, self.on_save)
        main_sizer.Add(save_btn, 0, wx.ALL|wx.CENTER, 5)
        self.SetSizer(main_sizer)

В этом примере вы создаете новую ссылку на StandardPaths и добавить wx.listbox Отказ Коробка списка удержит варианты фотографий, которые вы можете скачать. Он также автоматически добавляет прокрутку, если будет слишком много результатов, чтобы включить на экран одновременно. Вы звоните get_image_urls с прохожденным в Выбор объект, чтобы получить список URL-адреса Отказ Тогда вы зацикливаете на URL-адреса и извлечь те, которые имеют JPG в их имени. Это делает привод к вам отсутствует на альтернативных типах файлов изображений, таких как PNG или TIFF.

Это дает вам возможность улучшить этот код и улучшить его. Причина, по которой вы фильтруете URL-адреса, заключается в том, что результаты обычно имеют URL-адреса без изображения в смеси, и вы, вероятно, не хотите показывать те, что потенциально загружаемые, как это будет запутано пользователю.

Последний виджет, который будет добавлен, – кнопка «Сохранить». Вы также можете добавить кнопку «Отменить», но диалоговое окно имеет кнопку выхода вдоль верхней части, которая работает, поэтому она не требуется.

Теперь пришло время узнать, что get_image_urls () делает:

def get_image_urls(self, item):
    asset_url = f'https://images-api.nasa.gov/asset/{item.nasa_id}'
    image_request = requests.get(asset_url)
    image_json = image_request.json()
    try:
        image_urls = [url['href'] for url in image_json['collection']['items']]
    except:
        image_urls = []
    return image_urls

Этот обработчик событий активируется, когда пользователь нажимает кнопку «Сохранить». Когда пользователь пытается сохранить что-то, не выбирая элемент в списке, он вернет -1. Должен это случиться, вы показываете им Мессагедиал сказать им, что они могут захотеть что-то выбрать. Когда они что-то выбирают, вы покажете им wx.filedialog Это позволяет им выбрать, где сохранить файл и что это позвонить.

Обработчик событий вызывает Сохранить () Метод, так что это ваш следующий проект:

def save(self, path):
    selection = self.list_box.GetSelection()
    r = requests.get(
        self.list_box.GetClientData(selection))
    try:
        with open(path, "wb") as image:
            image.write(r.content)

        message = 'File saved successfully'
        with wx.MessageDialog(None, message=message,
                              caption='Save Successful',
                              style=wx.ICON_INFORMATION) as dlg:
            dlg.ShowModal()
    except:
        message = 'File failed to save!'
        with wx.MessageDialog(None, message=message,
                              caption='Save Failed',
                              style=wx.ICON_ERROR) as dlg:
            dlg.ShowModal()

Здесь вы получаете выбор снова и используете Запросы Пакет для загрузки изображения. Обратите внимание, что нет проверки, чтобы убедиться, что пользователь добавил расширение, пусть по праву. Вы можете добавить это, когда у вас есть шанс.

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

Если произойдет исключение, вы можете показать им диалог, который им тоже знает!

Вот что выглядит то диалог загрузки:

Теперь давайте добавим новую функциональность!

Добавление расширенного поиска

Есть несколько полей, которые вы можете использовать, чтобы помочь сузить поиск. Однако вы не хотите беспокоить свой пользовательский интерфейс с ними, если только пользователь не хочет использовать эти фильтры. Чтобы это позволить этому, вы можете добавить опцию «Расширенный поиск».

Добавление этой опции требует, чтобы вы немного переставили свой код, так что давайте скопируем свой nasa_search_ui.py Файл и ваш Download_dialog.py Модуль к новой папке под названием Версия_2 Отказ

Теперь переименуйте nasa_search_ui.py к main.py Чтобы сделать его более очевидным, какой скрипт является основной точкой входа для вашей программы. Чтобы сделать все более модульные, вы будете извлекать свои результаты поиска в свой собственный класс и иметь расширенный поиск в отдельном классе. Это означает, что у вас будут три панели в конце:

  • Главная панель
  • Панель результатов поиска
  • Расширенная панель поиска

Вот что будет выглядеть главное диалог, когда вы закончите:

Давайте перейдем на каждого из них отдельно.

Сценарий main.py

Главная Модуль – это ваша основная точка входа для вашего приложения. Точка входа – код, который ваш пользователь будет работать, чтобы запустить ваше приложение. Это также скрипт, который вы использовали, если вы должны были объединить свое приложение в исполняемый файл.

Давайте посмотрим, как ваш Главная Модуль запускается:

# main.py

import wx

from advanced_search import RegularSearch
from regular_search import SearchResults
from pubsub import pub


class MainPanel(wx.Panel):

    def __init__(self, parent):
        super().__init__(parent)
        pub.subscribe(self.update_ui, 'update_ui')

        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
        search_sizer = wx.BoxSizer()

Этот пример импортирует обе ваши панели поиска:

  • Расширенный поиск
  • Регулярный поиск

Это также использует Pubsub подписаться на обновленную тему.

Давайте узнаем, что еще в __init __ () :

txt = 'Search for images on NASA'
label = wx.StaticText(self, label=txt)
self.main_sizer.Add(label, 0, wx.ALL, 5)
self.search = wx.SearchCtrl(
    self, style=wx.TE_PROCESS_ENTER, size=(-1, 25))
self.search.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, self.on_search)
self.search.Bind(wx.EVT_TEXT_ENTER, self.on_search)
search_sizer.Add(self.search, 1, wx.EXPAND)

self.advanced_search_btn = wx.Button(self, label='Advanced Search',
                            size=(-1, 25))
self.advanced_search_btn.Bind(wx.EVT_BUTTON, self.on_advanced_search)
search_sizer.Add(self.advanced_search_btn, 0, wx.ALL, 5)
self.main_sizer.Add(search_sizer, 0, wx.EXPAND)

Здесь вы добавляете заголовок для страницы вместе с виджетом управления поиском, как вы выполнили ранее. Вы также добавляете новый Расширенный поиск Кнопка и используйте новую Sizer, чтобы содержать виджет поиска и кнопку. Затем вы добавляете, что Sizer на ваш главный Sizer.

Теперь давайте добавим панели:

self.search_panel = RegularSearch(self)
self.advanced_search_panel = AdvancedSearch(self)
self.advanced_search_panel.Hide()
self.main_sizer.Add(self.search_panel, 1, wx.EXPAND)
self.main_sizer.Add(self.advanced_search_panel, 1, wx.EXPAND)

В этом примере вы создали создание Регулярный поиск и AdvancedSearch Панели. Так как Регулярный поиск По умолчанию вы скрываете AdvancedSearch от пользователя при запуске.

Теперь давайте обновим on_earch () :

def on_search(self, event):
    search_results = []
    search_term = event.GetString()
    if search_term:
        query = {'q': search_term, 'media_type': 'image'}
        pub.sendMessage('search_results', query=query)

on_earch () Способ будет вызван, когда пользователь нажимает «Ввод/возврат» на своей клавиатуре или при нажатии значок кнопки поиска в виджете управления поиском. Если пользователь ввел строку поиска в элемент управления поиском, будет построен поисковый запрос, а затем отправляется с помощью Pubsub Отказ

Давайте узнаем, что произойдет, когда пользователь нажимает Расширенный поиск кнопка:

def on_advanced_search(self, event):
    self.search.Hide()
    self.search_panel.Hide()
    self.advanced_search_btn.Hide()
    self.advanced_search_panel.Show()
    self.main_sizer.Layout()

Когда on_advanced_search () Пожары, он скрывает виджет поиска, регулярную панель поиска и кнопку расширенной поиска. Далее он показывает расширенную панель поиска и звонки Макет () на main_sizer Отказ Это приведет к выключению панелей и изменений правильно разместится в рамках.

Последний метод создания IS update_ui () :

def update_ui(self):
    """
    Hide advanced search and re-show original screen

    Called by pubsub when advanced search is invoked
    """
    self.advanced_search_panel.Hide()
    self.search.Show()
    self.search_panel.Show()
    self.advanced_search_btn.Show()
    self.main_sizer.Layout()

update_ui () Метод называется, когда пользователь делает Расширенный поиск Отказ Этот метод вызывается Pubsub Отказ Это сделает реверс on_advanced_search () И не скрывайте все виджеты, которые были скрыты, когда была показана расширенная панель поиска. Он также будет скрывать расширенную панель поиска.

Код кадра то же самое, что было раньше, поэтому он не отображается здесь.

Давайте перейдем и узнаем, как создается регулярная панель поиска!

Сценарий rangor_search.py

Regular_search Модуль – ваш рекакторенный модуль, который содержит ObjectListView Это покажет ваши результаты поиска. Это также имеет Скачать Кнопка на нем.

Следующие методы/классы не будут покрыты, поскольку они такие же, как в предыдущей итерации:

  • on_download ()
  • on_selection ()
  • update_image ()
  • update_search_results ()
  • Класс результата

Давайте начнем, увидев, как выложены первые несколько строк в модуле:

# regular_search.py

import os
import requests
import wx

from download_dialog import DownloadDialog
from ObjectListView import ObjectListView, ColumnDefn
from pubsub import pub
from urllib.parse import urlencode, quote_plus

base_url = 'https://images-api.nasa.gov/search'

Здесь у вас есть все импорт, которые вы имели в оригинале nasa_search_ui.py Сценарий от version_1 Отказ У вас также есть base_url Что вам нужно сделать запросы на Image API NASA. Единственный новый импорт для Pubsub Отказ

Давайте пойдем вперед и создадим Регулярный поиск класс:

class RegularSearch(wx.Panel):

    def __init__(self, parent):
        super().__init__(parent)
        self.search_results = []
        self.max_size = 300
        font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.paths = wx.StandardPaths.Get()
        pub.subscribe(self.load_search_results, 'search_results')

        self.search_results_olv = ObjectListView(
            self, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
        self.search_results_olv.SetEmptyListMsg("No Results Found")
        self.search_results_olv.Bind(wx.EVT_LIST_ITEM_SELECTED,
                                     self.on_selection)
        main_sizer.Add(self.search_results_olv, 1, wx.EXPAND)
        self.update_search_results()

Этот код будет инициализировать search_results список в пустой список и установите max_size изображения. Это также устанавливает Sizer и ObjectListView Виджет, который вы используете для отображения результатов поиска для пользователя. Код на самом деле очень похож на первую итерацию кода, когда все классы объединены.

Вот остальная часть кода для __init __ () :

main_sizer.AddSpacer(30)
self.title = wx.TextCtrl(self, style=wx.TE_READONLY)
self.title.SetFont(font)
main_sizer.Add(self.title, 0, wx.ALL|wx.EXPAND, 5)
img = wx.Image(240, 240)
self.image_ctrl = wx.StaticBitmap(self,
                                  bitmap=wx.Bitmap(img))
main_sizer.Add(self.image_ctrl, 0, wx.CENTER|wx.ALL, 5
               )
download_btn = wx.Button(self, label='Download Image')
download_btn.Bind(wx.EVT_BUTTON, self.on_download)
main_sizer.Add(download_btn, 0, wx.ALL|wx.CENTER, 5)

self.SetSizer(main_sizer)

Первый предмет здесь – добавить проставку в main_sizer Отказ Тогда вы добавляете Название и IMG Связанные виджеты. Последний виджет, который будет добавлен, все еще кнопка загрузки.

Далее вам нужно будет написать новый метод:

def reset_image(self):
    img = wx.Image(240, 240)
    self.image_ctrl.SetBitmap(wx.Bitmap(img))
    self.Refresh()

RESET_IMAGE () Метод для сброса wx.cstaticbitmap Вернуться к пустому образу. Это может произойти, когда пользователь сначала использует обычный поиск, выбирает элемент, а затем решает выполнить расширенный поиск. Сброс изображения предотвращает пользователю, увидев ранее выбранный элемент и потенциально запутащий пользователя.

Последний метод, который вам нужно добавить, это load_search_results () :

def load_search_results(self, query):
    full_url = base_url + '?' + urlencode(query, quote_via=quote_plus)
    r = requests.get(full_url)
    data = r.json()
    self.search_results = []
    for item in data['collection']['items']:
        if item.get('data') and len(item.get('data')) > 0:
            data = item['data'][0]
            if data['title'].strip() == '':
                # Skip results with blank titles
                continue
            result = Result(item)
            self.search_results.append(result)
    self.update_search_results()
    self.reset_image()

load_search_results () Способ называется использованием Pubsub Отказ Оба Главная и Advanced_Search Модули называют его, пройдя в словаре запроса. Затем вы кодируете этот словарь в отформатированный URL. Далее вы используете Запросы Для отправки запроса JSON и вы затем извлеките результаты. Это также где вы звоните RESET_IMAGE () Так что, когда новый набор результатов нагрузки, результат не выбран результата.

Теперь вы готовы создать расширенный поиск!

Advanced_search.py Script

Advanced_Search Модуль – это wx.panel Это имеет все виджеты, которые вам нужно сделать расширенный поиск по API NASA. Если вы прочитаете свою документацию, вы обнаружите, что есть около дюжина фильтров, которые могут быть применены к поиску.

Давайте начнем с вершины:

class AdvancedSearch(wx.Panel):

    def __init__(self, parent):
        super().__init__(parent)

        self.main_sizer = wx.BoxSizer(wx.VERTICAL)

        self.free_text = wx.TextCtrl(self)
        self.ui_helper('Free text search:', self.free_text)
        self.nasa_center = wx.TextCtrl(self)
        self.ui_helper('NASA Center:', self.nasa_center)
        self.description = wx.TextCtrl(self)
        self.ui_helper('Description:', self.description)
        self.description_508 = wx.TextCtrl(self)
        self.ui_helper('Description 508:', self.description_508)
        self.keywords = wx.TextCtrl(self)
        self.ui_helper('Keywords (separate with commas):',
                       self.keywords)

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

Вот остальные фильтры:

self.location = wx.TextCtrl(self)
self.ui_helper('Location:', self.location)
self.nasa_id = wx.TextCtrl(self)
self.ui_helper('NASA ID:', self.nasa_id)
self.photographer = wx.TextCtrl(self)
self.ui_helper('Photographer:', self.photographer)
self.secondary_creator = wx.TextCtrl(self)
self.ui_helper('Secondary photographer:', self.secondary_creator)
self.title = wx.TextCtrl(self)
self.ui_helper('Title:', self.title)
search = wx.Button(self, label='Search')
search.Bind(wx.EVT_BUTTON, self.on_search)
self.main_sizer.Add(search, 0, wx.ALL | wx.CENTER, 5)

self.SetSizer(self.main_sizer)

В конце вы установили Sizer на main_sizer Отказ Обратите внимание, что не все фильтры, которые находятся в API NASA, реализуются в этом коде. Например, я не добавил Media_Type Поскольку это приложение будет жестко записано только для поиска изображений. Однако, если вы хотите аудио или видео, вы можете обновить это приложение для этого. Я также не включил ж/start и Годучик Фильтры. Не стесняйтесь добавлять тех, если хотите.

Теперь давайте перейдем и создаем ui_helper () Метод:

def ui_helper(self, label, textctrl):
    sizer = wx.BoxSizer()
    lbl = wx.StaticText(self, label=label, size=(150, -1))
    sizer.Add(lbl, 0, wx.ALL, 5)
    sizer.Add(textctrl, 1, wx.ALL | wx.EXPAND, 5)
    self.main_sizer.Add(sizer, 0, wx.EXPAND)

ui_helper () принимает текст метки и виджет управления текстом. Затем это создает WX.Boxsizer и а wx.statictext Отказ wx.statictext Добавляют в Sizer, как и пропущенный виджет текстового управления. Наконец, новый Sizer добавляется в main_sizer И тогда вы закончите. Это хороший способ уменьшить повторный код.

Последний пункт создать в этом классе on_earch () :

def on_search(self, event):
    query = {'q': self.free_text.GetValue(),
             'media_type': 'image',
             'center': self.nasa_center.GetValue(),
             'description': self.description.GetValue(),
             'description_508': self.description_508.GetValue(),
             'keywords': self.keywords.GetValue(),
             'location': self.location.GetValue(),
             'nasa_id': self.nasa_id.GetValue(),
             'photographer': self.photographer.GetValue(),
             'secondary_creator': self.secondary_creator.GetValue(),
             'title': self.title.GetValue()}
    pub.sendMessage('update_ui')
    pub.sendMessage('search_results', query=query)

Когда пользователь нажимает Поиск Кнопка, этот обработчик событий вызывается. Он создает поисковый запрос на основе того, что пользователь вводил в каждый из полей. Затем обработчик отправит два сообщения, используя Pubsub Отказ Первое сообщение будет обновлять пользовательский интерфейс, чтобы расширенный поиск скрыт, а результаты поиска отображаются. Второе сообщение фактически выполнит поиск по API NASA.

Вот как выглядит расширенная страница поиска:

Теперь давайте обновим диалоговое окно «Загрузить».

Сценарий Download_dialog.py

Диалог загрузки имеет пару минимальных изменений в нее. В основном вам нужно добавить импорт Python’s ОС Модуль, а затем обновите Сохранить () функция.

Добавьте следующие строки в начало функции:

def save(self, path):
    _, ext = os.path.splitext(path)
    if ext.lower() != '.jpg':
        path = f'{path}.jpg'

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

Обертывание

Эта статья охватывала большую забавную новую информацию. Вы выучили один подход для работы с открытым API, у которого уже нет обертки Python. Вы обнаружили важность чтения документации API, а затем добавили пользовательский интерфейс к этому API. Затем вы узнали, как разбирать JSON и загружать изображения из Интернета.

Пока он не покрыт здесь, у Python есть JSON Модуль, который вы могли бы также использовать.

Вот некоторые идеи для повышения этого приложения:

  • Кэширование результатов поиска
  • Загрузка миниатюр на заднем плане
  • Загрузка ссылок на заднем плане

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

Загрузите код

  • Исходный код как тарбол

Связанное чтение

  • Создание Калькулятор с wxpython.
  • Как распространять Приложение WXPYPHON