Schreiben einer Python-App zum Abrufen von Outlook-Mail, -Kalender und -Kontakten

In diesem Leitfaden werden Sie schrittweise durch den Prozess des Erstellens einer Python-App zum Abrufen von Nachrichten in Office 365 oder Outlook.com geführt. Wenn Sie die hier beschriebenen Schritte ausführen, sollte der Quellcode in diesem Repository das Ergebnis sein.

In diesem Leitfaden wird Microsoft Graph zum Zugriff auf Outlook-Mail verwendet. Microsoft empfiehlt die Verwendung von Microsoft Graph für den Zugriff auf Outlook-Mail, -Kalender und -Kontakte. Verwenden Sie die Outlook-APIs nur dann direkt (über https://outlook.office.com/api), wenn Sie ein Feature benötigen, das in den Graph-Endpunkten nicht verfügbar ist. Eine Version dieses Beispiels mit Verwendung der Outlook-APIs finden Sie in dieser Verzweigung.

In diesem Leitfaden wird davon ausgegangen, dass Sie Python und Django bereits installiert haben und auf Ihrem Entwicklungscomputer ausführen. Dieses Beispiel wurde mithilfe von Python, Version 3.5.2, und Django 1.10 erstellt.

Erstellen der App

Lassen Sie uns direkt loslegen. Öffnen Sie ein Eingabeaufforderungsfenster, und legen Sie das aktuelle Verzeichnis auf einen Ort fest, an dem Sie Ihren Code speichern möchten. Führen Sie den folgenden Befehl aus, um ein neues Django-Projekt zu erstellen.

django-admin.py startproject python_tutorial

Auf diese Weise wird ein neues Unterverzeichnis mit dem Namen python_tutorial erstellt. Ändern Sie das aktuelle Verzeichnis des Eingabeaufforderungsfensters in dieses neue Unterverzeichnis, und führen Sie den folgenden Befehl aus, um zu überprüfen, ob alles funktioniert.

python manage.py runserver

Öffnen Sie einen Webbrowser, sobald der Server gestartet wird, und navigieren Sie zu http://localhost:8000/. Es sollte eine Meldung angezeigt werden, dass der Vorgang erfolgreich war.

Die standardmäßige Django-Willkommensseite.

Wir wollen nun eine App zu unserem neuen Projekt hinzufügen, wie auch in der Meldung vorgeschlagen wird. Führen Sie an der Eingabeaufforderung den folgenden Befehl aus:

python manage.py startapp tutorial

Auf diese Weise wird ein neues Unterverzeichnis unter dem python_tutorial-Verzeichnis sowie eine Reihe von Dateien erstellt.

Öffnen Sie die Datei .\python_tutorial\settings.py, und fügen Sie die neue App tutorial zu der Einstellung INSTALLED_APPS hinzu.

Neuer Wert von INSTALLED_APPS in .\python_tutorial\settings.py

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'tutorial',
)

Speichern Sie Ihre Änderungen. Führen Sie über die Befehlszeile den folgenden Befehl aus, um die Datenbank für die App zu initialisieren.

python manage.py migrate

Fügen wir nun etwas Code zur App hinzu, um sicherzustellen, dass sie funktioniert. Öffnen Sie die Datei .\tutorial\views.py, und fügen Sie den folgenden Code hinzu.

home-Funktion in der Datei .\tutorial\views.py

from django.http import HttpResponse, HttpResponseRedirect

def home(request):
  return HttpResponse("Welcome to the tutorial.")

Erstellen Sie eine neue Datei im tutorial-Verzeichnis mit dem Namen urls.py. Fügen Sie dieser Datei den folgenden Code hinzu.

Inhalt der Datei .\tutorial\urls.py

from django.conf.urls import url 
from tutorial import views 

urlpatterns = [ 
  # The home view ('/tutorial/') 
  url(r'^$', views.home, name='home'), 
  # Explicit home ('/tutorial/home/') 
  url(r'^home/$', views.home, name='home'), 
]

Öffnen Sie nun die Datei .\python_tutorial\urls.py, und ersetzen Sie den Inhalt durch den folgenden Code:

Neuer Inhalt von .\python_tutorial\urls.py

from django.conf.urls import url, include
from django.contrib import admin
from tutorial import views

urlpatterns = [
    # Invoke the home view in the tutorial app by default
    url(r'^$', views.home, name='home'),
    # Defer any URLS to the /tutorial directory to the tutorial app
    url(r'^tutorial/', include('tutorial.urls', namespace='tutorial')),
    url(r'^admin/', include(admin.site.urls)),
]

Wenn Sie mit der Entwicklung in Django vertraut sind, ist dies nichts neu für Sie. Ansonsten haben wir nichts anderes getan, als Django mitzuteilen, wie Anforderungen an die Lernprogramm-App weitergeleitet werden sollen. Django sucht zuerst in der Datei .\python_tutorial\urls.py. Die neuen hinzugefügten Zeilen weisen Django an, Anforderungen an den Stamm an die Ansicht home in der Lernprogramm-App weiterzuleiten und alle Anforderungen an /tutorial/* an die Lernprogramm-App zu senden.

Die Einträge in der Datei .\tutorial\urls.py weisen Django an, Anforderungen entweder an /tutorial oder /tutorial/home an die Ansicht home zu senden. Die home-Funktion in .\tutorial\views.py gibt schließlich eine einfache HTTP-Antwort zurück.

Wenn Sie alle Ihre Änderungen speichern und zu http://localhost:8000 navigieren, sollte eine Willkommensmeldung angezeigt werden. Da wir nun bestätigt haben, dass die App funktioniert, können wir uns an die echte Arbeit machen.

Entwerfen der App

Unsere App ist sehr einfach. Wenn ein Benutzer die Website besucht, sieht er einen Link zum Anmelden und Anzeigen seiner E-Mails. Beim Klicken auf den Link gelangt er zur Azure-Anmeldeseite, wo er sich mit seinem Office 365- oder Outlook.com-Konto anmelden und unserer App Zugriff gewähren kann. Schließlich wird er zurück zu unserer App geleitet, die eine Liste der neuesten E-Mails im Posteingang des Benutzers anzeigt.

Beginnen wir nun damit, die statische Meldung durch etwas aussagekräftigeres zu ersetzen. Beginnen Sie, indem Sie ein neues Unterverzeichnis im Verzeichnis tutorial mit dem Namen templates erstellen. Erstellen Sie im Verzeichnis templates ein neues Unterverzeichnis mit dem Namen tutorial. Erstellen Sie schließlich eine neue Datei in diesem Verzeichnis mit dem Namen layout.html, und fügen Sie den folgenden Code hinzu. Dies ist ein Layout, das von allen Seiten in der App zum Hinzufügen von Bootstrap für das grundlegende Layout und die Formatierung und einer einfachen Navigationsleiste verwendet wird.

Inhalt der Datei .\tutorial\templates\tutorial\layout.html

<!DOCTYPE html>
<html>
<head>
  <title>Python Outlook API Sample</title>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body style="padding-top: 70px;">
  <nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="/">Python Outlook Sample</a>
      </div>
      <div id="navbar" class="navbar-collapse collapse">
        <ul class="nav navbar-nav authed-nav">
          <li id='home-nav'><a href="/">Home</a></li>
          <li id='inbox-nav'><a href="/tutorial/mail">Inbox</a></li>
          <li id='calendar-nav'><a href="/tutorial/events">Calendar</a></li>
          <li id='contacts-nav'><a href="/tutorial/contacts">Contacts</a></li>
        </ul>
      </div>
    </div>
  </nav>

  <div class="container main-container">
    {% block content %}{% endblock %}
  </div>

</body>
</html>

Als Nächstes werden wir eine einfache Startseite erstellen, um die Schaltfläche Verbindung mit Outlook herstellen anzuzeigen. Erstellen Sie eine Datei mit dem Namen home.html im Verzeichnis .\tutorial\templates\tutorial, und fügen Sie den folgenden Code hinzu.

Inhalt der Datei .\tutorial\templates\tutorial\home.html

{% extends "tutorial/layout.html" %}
{% block content %}
<div class="jumbotron">
  <h1>Python Outlook Sample</h1>
  <p>This example shows how to get an OAuth token from Azure using the <a href="https://docs.microsoft.com/de-de/azure/active-directory/develop/active-directory-v2-protocols-oauth-code" target="_blank">authorization code grant flow</a> and to use that token to make calls to the Outlook APIs.</p>
  <p>
    <a class="btn btn-lg btn-primary" href="{{signin_url}}" role="button" id="connect-button">Connect to Outlook</a>
  </p>
</div>
{% endblock %}

Nun wollen wir die home-Funktion in .\tutorial\views.py so ändern, dass diese neue Ansicht verwendet wird. Aktualisieren Sie die home-Funktion so, dass sie dem folgenden Code entspricht.

Aktualisierte home-Funktion

def home(request):
  sign_in_url = '#'
  context = { 'signin_url': sign_in_url }
  return render(request, 'tutorial/home.html', context)

Wie Sie sehen, ist unsere Startseite sehr einfach. Im Moment führt der Link noch keine Aktion aus, das werden wir aber bald ändern.

Registrieren der App

Wichtig

Neue App-Registrierungen sollten im Anwendungsregistrierungsportal erstellt und verwaltet werden, damit sie mit Outlook.com kompatibel sind. Erstellen Sie neue App-Registrierungen nur dann im Azure-Verwaltungsportal Folgendes auf Ihre App zutrifft:

  • Sie verwendet den OAuth2 Client Credentials Grant-Fluss oder
  • Sie muss neben Outlook auf andere Office 365-Arbeitslasten zugreifen (z. B. OneDrive for Business oder SharePoint)

Beachten Sie, dass mithilfe des Azure-Verwaltungsportals registrierte Apps nicht mit Outlook.com kompatibel sind und dass Berechtigungsbereiche nicht dynamisch angefordert werden können. Vorhandene App-Registrierungen, die im Azure-Verwaltungsportal erstellt wurden, funktionieren weiterhin nur für Office 365. Diese Registrierungen werden nicht im Anwendungsregistrierungsportal angezeigt und müssen im Azure-Verwaltungsportal verwaltet werden.

Kontoanforderungen

Um das Anwendungsregistrierungsportal zu verwenden, benötigen Sie entweder ein Office 365-Geschäfts- oder Schulkonto oder ein Microsoft-Konto. Wenn Sie nicht über eines dieser Konten verfügen, haben Sie verschiedene Möglichkeiten:

  • Registrieren Sie sich hier für ein neues Microsoft-Konto.
  • Sie haben mehrere Möglichkeiten, ein Office 365-Abonnement zu erhalten:

REST-API-Verfügbarkeit

Die REST-API ist derzeit auf allen Office 365-Konten, die über Exchange Online verfügen, sowie auf allen Outlook.com-Konten aktiviert.

Wechseln Sie zum App-Registrierungsportal, um schnell eine App-ID und einen geheimen Schlüssel abzurufen.

  1. Melden Sie sich über den Link Anmelden mit Ihrem Microsoft-Konto (Outlook.com) oder Ihrem Geschäfts-, Schul- oder Unikonto (Office 365) an.
  2. Klicken Sie auf die Schaltfläche App hinzufügen. Geben Sie python-tutorial für den Namen ein, und klicken Sie auf Anwendung erstellen.
  3. Suchen Sie den Abschnitt Anwendungsgeheimnisse, und klicken Sie auf die Schaltfläche Neues Kennwort generieren. Kopieren Sie nun das Kennwort, und speichern Sie es an einem sicheren Ort. Nachdem Sie das Kennwort kopiert haben, klicken Sie auf Ok.
  4. Suchen Sie den Abschnitt Plattformen, und klicken Sie auf Plattform hinzufügen. Wählen Sie Web aus, und geben Sie dann http://localhost:8000/tutorial/gettoken/ unter Umleitungs-URIs ein.
  5. Klicken Sie auf Speichern, um die Registrierung abzuschließen. Kopieren Sie die App-ID, und speichern Sie sie zusammen mit dem Kennwort, das Sie zuvor kopiert haben. Wir benötigen diese Werte bald.

So sollten die Details Ihrer App-Registrierung aussehen, wenn Sie fertig sind.

Screenshot der abgeschlossenen App-Registrierung im App-Registrierungsportal

Implementieren von OAuth2

Unser Ziel in diesem Abschnitt ist es, den Link auf unserer Startseite so einzurichten, dass der OAuth2-Autorisierungscodegenehmigungs-Fluss mit Azure AD initiiert wird. Der Einfachheit halber verwenden wir die Bibliothek Requests: HTTP for Humans, um alle unsere HTTP-Anforderungen zu verarbeiten. Geben Sie an der Eingabeaufforderung den folgenden Befehl ein.

pip install requests

Jetzt ist die Bibliothek installiert und kann verwendet werden. Erstellen Sie eine neue Datei im tutorial-Verzeichnis mit dem Namen authhelper.py. Wir beginnen, indem wir zunächst eine Funktion zum Generieren der Anmelde-URL definieren.

Inhalt der Datei .\tutorial\authhelper.py

from urllib.parse import quote, urlencode
import base64
import json
import time

# Client ID and secret
client_id = 'YOUR APP ID HERE'
client_secret = 'YOUR APP PASSWORD HERE'

# Constant strings for OAuth2 flow
# The OAuth authority
authority = 'https://login.microsoftonline.com'

# The authorize URL that initiates the OAuth2 client credential flow for admin consent
authorize_url = '{0}{1}'.format(authority, '/common/oauth2/v2.0/authorize?{0}')

# The token issuing endpoint
token_url = '{0}{1}'.format(authority, '/common/oauth2/v2.0/token')

# The scopes required by the app
scopes = [ 'openid',
           'User.Read',
           'Mail.Read' ]

def get_signin_url(redirect_uri):
  # Build the query parameters for the signin url
  params = { 'client_id': client_id,
             'redirect_uri': redirect_uri,
             'response_type': 'code',
             'scope': ' '.join(str(i) for i in scopes)
            }

  signin_url = authorize_url.format(urlencode(params))

  return signin_url

Ersetzen Sie die Platzhalter YOUR APP ID HERE und YOUR APP PASSWORD HERE durch die Werte, die Sie in Schritt 3 generiert haben, und speichern Sie die Änderungen.

Da wir nun über tatsächliche Werte für die Client-ID und den geheimen Clientschlüssel verfügen, können wir mit der neuen Funktion arbeiten. Ändern Sie die home-Funktion in der Datei .\tutorial\views.py so, dass die get_signin_url-Funktion verwendet wird, um den Link auszufüllen. Die Funktion verwendet einen Parameter, und zwar redirect_uri. Dieser Wert wird verwendet, um die URL in der App festzulegen, zu der Azure eine Umleitung ausführt, nachdem die Anmeldung abgeschlossen ist. Wir fahren nun fort und erstellen eine Platzhalteransicht, mit dem Namen gettoken, die als Umleitungsziel fungiert.

Aktualisierte Inhalte von .\tutorial\views.py

from django.shortcuts import render
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse
from tutorial.authhelper import get_signin_url

# Create your views here.

def home(request):
  redirect_uri = request.build_absolute_uri(reverse('tutorial:gettoken'))
  sign_in_url = get_signin_url(redirect_uri)
  return HttpResponse('<a href="' + sign_in_url +'">Click here to sign in and view your mail</a>')

def gettoken(request):
  return HttpResponse('gettoken view')

In der Ansicht werden derzeit nicht viele Aktionen ausgeführt, dies werden wir aber bald ändern. Fügen Sie diese neue Ansicht der Datei .\tutorials\urls.py hinzu.

Aktualisierter Inhalt der Datei .\tutorials\urls.py

from django.conf.urls import url 
from tutorial import views 

urlpatterns = [
  # The home view ('/tutorial/') 
  url(r'^$', views.home, name='home'), 
  # Explicit home ('/tutorial/home/') 
  url(r'^home/$', views.home, name='home'), 
  # Redirect to get token ('/tutorial/gettoken/')
  url(r'^gettoken/$', views.gettoken, name='gettoken'),
]

Speichern Sie Ihre Änderungen, und navigieren Sie zu http://localhost:8000. Wenn Sie den Mauszeiger über den Link bewegen, sollte dies folgendermaßen aussehen:

https://login.microsoftonline.com/common/oauth2/v2.0/authorize?scope=openid+User.Read+Mail.Read&response_type=code&client_id=<SOME GUID>&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Ftutorial%2Fgettoken%2F

Der Abschnitt <SOME GUID> sollte mit Ihrer Client-ID übereinstimmen. Klicken Sie auf den Link. Daraufhin wird die Anmeldeseite angezeigt. Melden Sie sich mit Ihrem Office 365- oder Outlook.com-Konto an. Der Browser sollte nun zurück an die Ansicht gettoken geleitet werden. Die Ansicht ist noch inaktiv, also lassen Sie uns das nun ändern.

Austauschen des Codes durch ein Token

Als Erstes müssen wir den Autorisierungscode aus der Anforderung extrahieren. Wenn Azure eine Umleitung zu unserer gettoken-Funktion ausführt, ist ein code-Abfrageparameter enthalten, der den Autorisierungscode enthält. Aktualisieren Sie die gettoken-Funktion so, dass der Wert dieses Parameters abgerufen und angezeigt wird.

Aktualisierte gettoken-Funktion in .\tutorial\views.py

def gettoken(request):
  auth_code = request.GET['code']
  return HttpResponse('Authorization code: {0}'.format(auth_code))

Speichern Sie Ihre Änderungen, und wiederholen Sie den Vorgang der Anmeldung bei der App. Anstelle einer statischen Meldung sollte nun der Wert des Autorisierungscodes auf dem Bildschirm angezeigt werden. Das ist zwar schon besser, aber immer noch nicht sehr hilfreich. Lassen Sie uns nun tatsächlich etwas mit dem Code ausführen.

Importieren Sie zuerst die requests-Bibliothek, indem Sie die folgende Zeile oben in .\tutorial\authhelper.py hinzufügen.

import requests

Fügen Sie nun eine weitere Hilfsfunktion zu authhelper.py mit dem Namen get_token_from_code hinzu.

get_token_from_code in der Datei .\tutorial\authhelper.py

def get_token_from_code(auth_code, redirect_uri):
  # Build the post form for the token request
  post_data = { 'grant_type': 'authorization_code',
                'code': auth_code,
                'redirect_uri': redirect_uri,
                'scope': ' '.join(str(i) for i in scopes),
                'client_id': client_id,
                'client_secret': client_secret
              }

  r = requests.post(token_url, data = post_data)

  try:
    return r.json()
  except:
    return 'Error retrieving token: {0} - {1}'.format(r.status_code, r.text)

Abrufen der E-Mail-Adresse des Benutzers

Wir verwenden das Zugriffstoken das erste Mal, um die E-Mail-Adresse des Benutzers aus der Outlook-API abzurufen. Sie werden bald sehen, warum wir das tun.

Erstellen Sie eine neue Datei im tutorial-Verzeichnis mit dem Namen outlookservice.py. In diese Datei implementieren wir alle Outlook-API-Funktionen. Wir beginnen, indem wir eine allgemeine Methode zum Senden von API-Anforderungen mit dem Namen make_api_call erstellen.

Inhalt von ./tutorial/outlookservice.py

import requests
import uuid
import json

graph_endpoint = 'https://graph.microsoft.com/v1.0{0}'

# Generic API Sending
def make_api_call(method, url, token, user_email, payload = None, parameters = None):
  # Send these headers with all API calls
  headers = { 'User-Agent' : 'python_tutorial/1.0',
              'Authorization' : 'Bearer {0}'.format(token),
              'Accept' : 'application/json',
              'X-AnchorMailbox' : user_email }

  # Use these headers to instrument calls. Makes it easier
  # to correlate requests and responses in case of problems
  # and is a recommended best practice.
  request_id = str(uuid.uuid4())
  instrumentation = { 'client-request-id' : request_id,
                      'return-client-request-id' : 'true' }

  headers.update(instrumentation)

  response = None

  if (method.upper() == 'GET'):
      response = requests.get(url, headers = headers, params = parameters)
  elif (method.upper() == 'DELETE'):
      response = requests.delete(url, headers = headers, params = parameters)
  elif (method.upper() == 'PATCH'):
      headers.update({ 'Content-Type' : 'application/json' })
      response = requests.patch(url, headers = headers, data = json.dumps(payload), params = parameters)
  elif (method.upper() == 'POST'):
      headers.update({ 'Content-Type' : 'application/json' })
      response = requests.post(url, headers = headers, data = json.dumps(payload), params = parameters)

  return response

Diese Funktion verwendet die requests-Bibliothek zum Senden von API-Anforderungen. Sie legt einen Standardsatz von Headern für jede Anforderung fest, einschließlich der Clientinstrumentation.

Außerdem wird die E-Mail-Adresse verwendet, die wir aus dem ID-Token abgerufen haben, um den X-AnchorMailbox-Header festzulegen. Durch Festlegen dieses Headers kann der API-Endpunkt API-Aufrufe effizienter an den korrekten Back-End-Postfachserver weiterleiten. Deshalb möchten wir die E-Mail-Adresse des Benutzers abrufen.

Als Nächstes erstellen wir eine Funktion, um die make_api_call-Funktion zum Abrufen des Benutzers zu verwenden. Fügen Sie eine Funktion mit dem Namen get_me zu outlookservice.py hinzu.

Die Funktion get_me in ./tutorial/outlookservice.py

def get_me(access_token):
  get_me_url = graph_endpoint.format('/me')

  # Use OData query parameters to control the results
  #  - Only return the displayName and mail fields
  query_parameters = {'$select': 'displayName,mail'}

  r = make_api_call('GET', get_me_url, access_token, "", parameters = query_parameters)

  if (r.status_code == requests.codes.ok):
    return r.json()
  else:
    return "{0}: {1}".format(r.status_code, r.text)

Nun möchten wir sicherstellen, dass diese funktioniert. Ändern Sie die gettoken-Funktion views.py so, dass die folgenden Hilfsfunktionen verwendet und die Rückgabewerte angezeigt werden.

Aktualisierte gettoken-Funktion in .\tutorial\views.py

# Add import statement to include new function
from tutorial.outlookservice import get_me

def gettoken(request):
  auth_code = request.GET['code']
  redirect_uri = request.build_absolute_uri(reverse('tutorial:gettoken'))
  token = get_token_from_code(auth_code, redirect_uri)
  access_token = token['access_token']
  user = get_me(access_token)

  # Save the token in the session
  request.session['access_token'] = access_token
  request.session['user_email'] = user['mail']
  return HttpResponse('User Email: {0}, Access token: {1}'.format(user['mail'], access_token))

Wenn Sie Ihre Änderungen speichern, den Server neu starten und den Anmeldevorgang erneut durchlaufen, sollte eine lange Zeichenfolge von scheinbar unsinnigen Zeichen angezeigt werden. Wenn alles nach Plan läuft, sollte dies ein Zugriffstoken sein.

Aktualisieren des Zugriffstokens

Von Azure zurückgegebene Zugriffstoken sind eine Stunde lang gültig. Wenn Sie das Token verwenden, nachdem es abgelaufen ist, geben die API-Aufrufe 401-Fehler zurück. Sie könnten den Benutzer bitten, sich erneut anzumelden, aber die bessere Option besteht darin, das Token automatisch zu aktualisieren.

Zu diesem Zweck muss die App den offline_access-Bereich anfordern. Fügen Sie diesen Bereich dem scopes-Array in authhelper.py hinzu:

# The scopes required by the app
scopes = [ 'openid',
           'offline_access',
           'User.Read',
           'Mail.Read' ]

Dies bewirkt, dass die Tokenantwort von Azure ein Aktualisierungstoken enthält. Wir werden nun gettoken in views.py so aktualisieren, dass das Aktualisierungstoken und die Ablaufzeit in der Sitzung gespeichert werden.

Aktualisierte gettoken-Funktion in .\tutorial\views.py

# Add import statement to include new function
from tutorial.outlookservice import get_me

def gettoken(request):
  auth_code = request.GET['code']
  redirect_uri = request.build_absolute_uri(reverse('tutorial:gettoken'))
  token = get_token_from_code(auth_code, redirect_uri)
  access_token = token['access_token']
  user = get_me(access_token)
  refresh_token = token['refresh_token']
  expires_in = token['expires_in']

  # expires_in is in seconds
  # Get current timestamp (seconds since Unix Epoch) and
  # add expires_in to get expiration time
  # Subtract 5 minutes to allow for clock differences
  expiration = int(time.time()) + expires_in - 300

  # Save the token in the session
  request.session['access_token'] = access_token
  request.session['refresh_token'] = refresh_token
  request.session['token_expires'] = expiration
  request.session['user_email'] = user['mail']
  return HttpResponse('User Email: {0}, Access token: {1}'.format(user['mail'], access_token))

Lassen Sie uns nun eine Funktion zum Aktualisieren des Zugriffstokens erstellen. Fügen Sie die folgende Funktion zu authhelper.py hinzu:

Die Funktion get_token_from_refresh_token in ./tutorial/authhelper.py

def get_token_from_refresh_token(refresh_token, redirect_uri):
  # Build the post form for the token request
  post_data = { 'grant_type': 'refresh_token',
                'refresh_token': refresh_token,
                'redirect_uri': redirect_uri,
                'scope': ' '.join(str(i) for i in scopes),
                'client_id': client_id,
                'client_secret': client_secret
              }

  r = requests.post(token_url, data = post_data)

  try:
    return r.json()
  except:
    return 'Error retrieving token: {0} - {1}'.format(r.status_code, r.text)

Jetzt erstellen wir abschließen eine Hilfsfunktion, um das Zugriffstoken abzurufen. Die Funktion überprüft den Ablaufzeitpunkt und aktualisiert diesen, falls das Token abgelaufen ist. Andernfalls wird lediglich das Zugriffstoken aus der Sitzung zurückgegeben. Fügen Sie die folgende Funktion zu authhelper.py hinzu:

Die Funktion get_access_token in ./tutorial/authhelper.py

def get_access_token(request, redirect_uri):
  current_token = request.session['access_token']
  expiration = request.session['token_expires']
  now = int(time.time())
  if (current_token and now < expiration):
    # Token still valid
    return current_token
  else:
    # Token expired
    refresh_token = request.session['refresh_token']
    new_tokens = get_token_from_refresh_token(refresh_token, redirect_uri)

    # Update session
    # expires_in is in seconds
    # Get current timestamp (seconds since Unix Epoch) and
    # add expires_in to get expiration time
    # Subtract 5 minutes to allow for clock differences
    expiration = int(time.time()) + new_tokens['expires_in'] - 300

    # Save the token in the session
    request.session['access_token'] = new_tokens['access_token']
    request.session['refresh_token'] = new_tokens['refresh_token']
    request.session['token_expires'] = expiration

    return new_tokens['access_token']

Importieren Sie nun get_access_token in views.py, damit wir dieses verwenden können.

from tutorial.authhelper import get_signin_url, get_token_from_code, get_access_token

Verwenden der Mail-API

Da wir nun ein Zugriffstoken abrufen können, bietet es sich an, mit der Mail-API fortzufahren. Beginnen wir mit dem Erstellen einer mail-Ansicht in views.py.

mail-Funktion in .\tutorial\views.py

def mail(request):
  access_token = get_access_token(request, request.build_absolute_uri(reverse('tutorial:gettoken')))
  user_email = request.session['user_email']
  # If there is no token in the session, redirect to home
  if not access_token:
    return HttpResponseRedirect(reverse('tutorial:home'))
  else:
    return HttpResponse('Access token found in session: {0}'.format(access_token))

Aktualisieren Sie die Datei urls.py so, dass ein Eintrag für die neue Ansicht eingeschlossen wird.

Aktualisierte Inhalte von .\tutorial\urls.py

from django.conf.urls import url
from tutorial import views 

urlpatterns = [ 
  # The home view ('/tutorial/') 
  url(r'^$', views.home, name='home'), 
  # Explicit home ('/tutorial/home/') 
  url(r'^home/$', views.home, name='home'), 
  # Redirect to get token ('/tutorial/gettoken/')
  url(r'^gettoken/$', views.gettoken, name='gettoken'),
  # Mail view ('/tutorial/mail/')
  url(r'^mail/$', views.mail, name='mail'),
]

Aktualisieren Sie die gettoken-Funktion so, dass eine Umleitung an die mail-Ansicht erfolgt, nachdem das Token in der Sitzung gespeichert wurde.

Aktualisierte gettoken-Funktion in .\tutorial\views.py

def gettoken(request):
  auth_code = request.GET['code']
  redirect_uri = request.build_absolute_uri(reverse('tutorial:gettoken'))
  token = get_token_from_code(auth_code, redirect_uri)
  access_token = token['access_token']
  user = get_me(access_token)
  refresh_token = token['refresh_token']
  expires_in = token['expires_in']

  # expires_in is in seconds
  # Get current timestamp (seconds since Unix Epoch) and
  # add expires_in to get expiration time
  # Subtract 5 minutes to allow for clock differences
  expiration = int(time.time()) + expires_in - 300

  # Save the token in the session
  request.session['access_token'] = access_token
  request.session['refresh_token'] = refresh_token
  request.session['token_expires'] = expiration
  request.session['user_email'] = user['mail']
  return HttpResponseRedirect(reverse('tutorial:mail'))

Derzeit liest diese Funktion das Token lediglich aus dem Cookie und zeigt es an. Speichern Sie Ihre Änderungen, starten Sie den Server neu, und durchlaufen Sie den Anmeldevorgang erneut. Das Token sollte angezeigt werden. Da wir nun wissen, dass wir Zugriff auf das Token in der mail-Funktion haben, können wir die Mail-API aufrufen.

Wir fügen nun eine Funktion hinzu, die die make_api_call-Funktion verwendet, um eine Anforderung zum Abrufen von Nachrichten aus dem Posteingang zu implementieren. Erstellen Sie eine neue Funktion in outlookservice.py mit dem Namen get_my_messages.

Die Funktion get_my_messages in ./tutorial/outlookservice.py

def get_my_messages(access_token, user_email):
  get_messages_url = graph_endpoint.format('/me/mailfolders/inbox/messages')

  # Use OData query parameters to control the results
  #  - Only first 10 results returned
  #  - Only return the ReceivedDateTime, Subject, and From fields
  #  - Sort the results by the ReceivedDateTime field in descending order
  query_parameters = {'$top': '10',
                      '$select': 'receivedDateTime,subject,from',
                      '$orderby': 'receivedDateTime DESC'}

  r = make_api_call('GET', get_messages_url, access_token, user_email, parameters = query_parameters)

  if (r.status_code == requests.codes.ok):
    return r.json()
  else:
    return "{0}: {1}".format(r.status_code, r.text)

Nun können wir die mail-Funktion so ändern, dass diese Funktion aufgerufen und E-Mails abgerufen werden. Importieren Sie zuerst die get_my_messages-Funktion, indem Sie die folgende Zeile oben in views.py hinzufügen.

from tutorial.outlookservice import get_me, get_my_messages

Aktualisieren Sie dann die mail-Funktion, um die neue Funktion aufrufen.

Neue Version der mail-Funktion in ./tutorial/views.py

def mail(request):
  access_token = get_access_token(request, request.build_absolute_uri(reverse('tutorial:gettoken')))
  user_email = request.session['user_email']
  # If there is no token in the session, redirect to home
  if not access_token:
    return HttpResponseRedirect(reverse('tutorial:home'))
  else:
    messages = get_my_messages(access_token, user_email)
    return HttpResponse('Messages: {0}'.format(messages))

Wenn Sie die Änderungen speichern und sich bei der App anmelden, sollte nun eine unformatierte Auflistung der JSON-Antwort angezeigt werden.

Anzeigen der Ergebnisse

Während die aktuelle Auflistung von Nachrichten bestätigt, dass die API-Aufrufe funktionieren, können wir die Django-Vorlagen verwenden, um die Ergebnisse in einer benutzerfreundlicheren Weise anzuzeigen.

Erstellen Sie eine neue Datei im Verzeichnis ./tutorial/templates/tutorial mit dem Namen mail.html, und fügen Sie den folgenden Code hinzu.

Inhalt der Datei ./tutorial/templates/tutorial/mail.html

{% extends "tutorial/layout.html" %}
{% block content %}
<h1>Your Email</h1>
<table class="table">
  <tr>
    <th>From</th>
    <th>Subject</th>
    <th>Received</th>
  </tr>

  {% for message in messages %}
    <tr>
      <td>{{ message.from.emailAddress.name }}</td>
      <td>{{ message.subject }}</td>
      <td>{{ message.receivedDateTime }}</td>
    </tr>
  {% endfor %}
</table>
{% endblock %}

Aktualisieren Sie die mail -Funktion in views.py so, dass diese neue Vorlage verwendet wird.

Aktualisierte mail-Funktion in ./tutorial/views.py

def mail(request):
  access_token = get_access_token(request, request.build_absolute_uri(reverse('tutorial:gettoken')))
  user_email = request.session['user_email']
  # If there is no token in the session, redirect to home
  if not access_token:
    return HttpResponseRedirect(reverse('tutorial:home'))
  else:
    messages = get_my_messages(access_token, user_email)
    context = { 'messages': messages['value'] }
    return render(request, 'tutorial/mail.html', context)

Speichern Sie die Änderungen, und melden Sie sich bei der App an. Nun sollte eine einfache Tabelle von Nachrichten in Ihrem Posteingang angezeigt werden.

Die fertige App, in der der Posteingang des Benutzers angezeigt wird.

Hinzufügen von Kalender- und Kontakte-APIs

Da Sie nun das Aufrufen der Outlook-Mail-API gemeistert haben, dürfte es kein Problem mehr sein, das gleiche für Kalender- und Kontakte-APIs zu tun.

Tipp

Wenn Sie die Schritte in diesem Lernprogramm befolgt haben, haben Sie wahrscheinlich ein Zugriffstoken in Ihrem Sitzungscookie gespeichert. Dieses Token ist nur für den Mail.Read-Bereich gültig. Um die Kalender- oder Kontakte-API aufzurufen, müssen wir neue Bereiche hinzufügen. Starten Sie den Browser unbedingt neu, um das Sitzungscookie zu entfernen, damit Sie den Anmeldevorgang von vorne beginnen können, um ein neues Zugriffstoken zu erhalten.

Für die Kalender-API:

  1. Aktualisieren Sie das scopes-Array in authhelper.py, um den Calendars.Read-Bereich einzuschließen.

    # The scopes required by the app
    scopes = [ 'openid', 
               'offline_access',
               'User.Read',
               'Mail.Read',
               'Calendars.Read' ]
    
  2. Fügen Sie eine neue Funktion zu outlookservice.py mit dem Namen get_my_events hinzu.

    def get_my_events(access_token, user_email):
      get_events_url = graph_endpoint.format('/me/events')
    
      # Use OData query parameters to control the results
      #  - Only first 10 results returned
      #  - Only return the Subject, Start, and End fields
      #  - Sort the results by the Start field in ascending order
      query_parameters = {'$top': '10',
                          '$select': 'subject,start,end',
                          '$orderby': 'start/dateTime ASC'}
    
      r = make_api_call('GET', get_events_url, access_token, user_email, parameters = query_parameters)
    
      if (r.status_code == requests.codes.ok):
        return r.json()
      else:
        return "{0}: {1}".format(r.status_code, r.text)
    
  3. Importieren Sie in views.py die get_my_events-Funktion.

    from tutorial.outlookservice import get_my_events
    
  4. Fügen Sie eine neue Ansicht zu views.py mit dem Namen events hinzu.

    def events(request):
      access_token = get_access_token(request, request.build_absolute_uri(reverse('tutorial:gettoken')))
      user_email = request.session['user_email']
      # If there is no token in the session, redirect to home
      if not access_token:
        return HttpResponseRedirect(reverse('tutorial:home'))
      else:
        events = get_my_events(access_token, user_email)
        context = { 'events': events['value'] }
        return render(request, 'tutorial/events.html', context)
    
  5. Fügen Sie die neue Ansicht zu ./tutorial/urls.py hinzu.

    # Events view ('/tutorial/events/')
    url(r'^events/$', views.events, name='events'),
    
  6. Fügen Sie eine neue events.html-Vorlage zu ./tutorial/templates/tutorial directory hinzu.

    {% extends "tutorial/layout.html" %}
    {% block content %}
    <h1>Your Events</h1>
    <table class="table">
      <tr>
        <th>Subject</th>
        <th>Start</th>
        <th>End</th>
      </tr>
    
      {% for event in events %}
        <tr>
          <td>{{ event.subject }}</td>
          <td>{{ event.start.dateTime }} ({{ event.start.timeZone }})</td>
          <td>{{ event.end.dateTime }} ({{ event.end.timeZone }})</td>
        </tr>
      {% endfor %}
    </table>
    {% endblock %}
    
  7. Speichern Sie alle Änderungen speichern. Navigieren Sie zu http://localhost:8000/tutorial/events, nachdem Sie sich bei der App angemeldet haben.

Für die Kontakte-API:

  1. Aktualisieren Sie das scopes-Array in authhelper.py, um den Contacts.Read-Bereich einzuschließen.

    # The scopes required by the app
    scopes = [ 'openid', 
               'offline_access',
               'User.Read',
               'Mail.Read',
               'Contacts.Read' ]
    
  2. Fügen Sie eine neue Funktion zu outlookservice.py mit dem Namen get_my_contacts hinzu.

    def get_my_contacts(access_token, user_email):
      get_contacts_url = graph_endpoint.format('/me/contacts')
    
      # Use OData query parameters to control the results
      #  - Only first 10 results returned
      #  - Only return the GivenName, Surname, and EmailAddresses fields
      #  - Sort the results by the GivenName field in ascending order
      query_parameters = {'$top': '10',
                          '$select': 'givenName,surname,emailAddresses',
                          '$orderby': 'givenName ASC'}
    
      r = make_api_call('GET', get_contacts_url, access_token, user_email, parameters = query_parameters)
    
      if (r.status_code == requests.codes.ok):
        return r.json()
      else:
        return "{0}: {1}".format(r.status_code, r.text)
    
  3. Importieren Sie in views.py die get_my_contacts-Funktion.

    from tutorial.outlookservice import get_my_contacts
    
  4. Fügen Sie eine neue Ansicht zu views.py mit dem Namen contacts hinzu.

    def contacts(request):
      access_token = get_access_token(request, request.build_absolute_uri(reverse('tutorial:gettoken')))
      user_email = request.session['user_email']
      # If there is no token in the session, redirect to home
      if not access_token:
        return HttpResponseRedirect(reverse('tutorial:home'))
      else:
        contacts = get_my_contacts(access_token, user_email)
        context = { 'contacts': contacts['value'] }
        return render(request, 'tutorial/contacts.html', context)
    
  5. Fügen Sie die neue Ansicht zu ./tutorial/urls.py hinzu.

    # Contacts view ('/tutorial/contacts/')
    url(r'^contacts/$', views.contacts, name='contacts'),
    
  6. Fügen Sie eine neue contacts.html-Vorlage zu ./tutorial/templates/tutorial directory hinzu.

    {% extends "tutorial/layout.html" %}
    {% block content %}
    <h1>Your Contacts</h1>
    <table class="table">
      <tr>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Email Address</th>
      </tr>
    
      {% for contact in contacts %}
        <tr>
          <td>{{ contact.givenName }}</td>
          <td>{{ contact.surname }}</td>
          <td>{{ contact.emailAddresses.0.address }}</td>
        </tr>
      {% endfor %}
    </table>
    {% endblock %}
    
  7. Speichern Sie alle Änderungen speichern. Navigieren Sie zu http://localhost:8000/tutorial/contacts, nachdem Sie sich bei der App angemeldet haben.