Webapplikationer börjar vanligtvis enkelt men kan bli ganska komplexa, och de flesta av dem överskrider snabbt ansvaret för att endast svara på HTTP-förfrågningar.
När det händer måste man skilja mellan vad som måste hända omedelbart (vanligtvis i HTTP-begäran livscykel) och vad som kan hända så småningom. Varför är det så? Tja, för när din ansökan blir överbelastad med trafik, gör det enkla saker som detta.
Verksamheten i en webbapplikation kan klassificeras som kritisk eller förfrågad tid och bakgrundsuppgifter, de som händer utanför begäranstid. Dessa kartor till de som beskrivs ovan:
Request-time-operationer kan göras på en enda begäran / svarscykel utan att oroa sig för att operationen kommer att gå ut eller att användaren kan ha en dålig upplevelse. Vanliga exempel inkluderar databasfunktionerna CRUD (Skapa, läs, uppdatera, ta bort) och användarhantering (inloggning / utloggning).
Bakgrundsuppgifter är olika, eftersom de oftast är ganska tidskrävande och är benägna att misslyckas, främst på grund av externa beroenden. Några vanliga scenarier bland komplexa webbapplikationer är:
Bakgrundsuppgifter är huvudfokus för denna handledning. Det vanligaste programmeringsmönstret som används för detta scenario är producentens konsumentarkitektur.
I enkla termer kan denna arkitektur beskrivas så här:
Vanligtvis hämtar konsumenterna uppgifter från kön i ett första-i-först-ut-format (FIFO) eller enligt deras prioriteringar. Konsumenterna kallas också som arbetare, och det är termen som vi kommer att använda hela, eftersom det är förenligt med terminologin som används av den diskuterade tekniken.
Vilken typ av uppgifter kan behandlas i bakgrunden? Uppgifter som:
Selleri är de facto valet för att göra bakgrundsuppgift bearbetning i Python / Django ekosystemet. Den har ett enkelt och tydligt API, och det integrerar vackert med Django. Den stöder olika tekniker för uppgiftskön och olika paradigmer för arbetarna.
I den här handledningen kommer vi att skapa en Django-leksaks webbapplikation (hantera verkliga scenarier) som använder bakgrundsuppgiftsbehandling.
Om du antar att du redan är bekant med Python-pakethantering och virtuella miljöer, låt oss installera Django:
$ pip installera Django
Jag har bestämt mig för att bygga ytterligare en blogga applikation. Programmets fokus ligger på enkelhet. En användare kan helt enkelt skapa ett konto och utan för mycket krångel kan skapa ett inlägg och publicera det på plattformen.
Ställ in quick_publisher
Django-projektet:
$ django-admin startprojekt quick_publisher
Låt oss börja appen:
$ cd quick_publisher $ ./manage.py startapp main
När jag börjar ett nytt Django-projekt tycker jag om att skapa en huvud
applikation som bland annat innehåller en anpassad användarmodell. Ofta än inte, stöter jag på begränsningar av standard Django Användare
modell. Har en egen Användare
modellen ger oss fördelen av flexibilitet.
# main / models.py från django.db importmodeller från django.contrib.auth.models importera AbstractBaseUser, PermissionsMixin, BaseUserManager klass UserAccountManager (BaseUserManager): use_in_migrations = Sann def_create_user (själv, email, lösenord, ** extra_fields): om inte e-post: hämta ValueError ('E-postadress måste anges') om inte lösenord: höja ValueError ('Lösenord måste anges') email = self.normalize_email (email) user = self.model (email = email, ** extra_fields) user.set_password (password) user.save (using = self._db) returnera användaren def create_user (själv, email = Ingen, lösenord = Ingen, ** extra_fields): returnera self._create_user (email, password, ** extra_fields) def Create_superuser (self, email, password, ** extra_fields): extra_fields ['is_staff'] = Sann extra_fields ['is_superuser'] = Sann retur själv._create_user (email, password, ** extra_fields) klass Användare (AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = [] USERNAME_FIELD = 'email' objects = UserAccountManager () email = models.EmailField ('email', unikt = True, blank = False, null = False) full_name = models.CharField ('fullständigt namn', blank = Sann, null = True, max_length = 400) is_staff = models.BooleanField , default = False) is_active = models.BooleanField ('aktiv', default = True) def get_short_name (self): return self.email def get_full_name (själv): returnera self.email def __unicode __ (själv): returnera self.email
Var noga med att kolla in Django-dokumentationen om du inte är bekant med hur anpassade användarmodeller fungerar.
Nu måste vi berätta för Django att använda den här användarmodellen istället för standarden. Lägg till den här raden i quick_publisher / settings.py
fil:
AUTH_USER_MODEL = 'main.User'
Vi måste också lägga till huvud
ansökan till INSTALLED_APPS
lista i quick_publisher / settings.py
fil. Vi kan nu skapa migreringar, tillämpa dem och skapa en superanvändare för att kunna logga in på Django admin panel:
$ ./manage.py makemigrations huvud $ ./manage.py migrera $ ./manage.py createsuperuser
Låt oss nu skapa en separat Django-applikation som ansvarar för inlägg:
$ ./manage.py startapp publicera
Låt oss definiera en enkel Post-modell i utgivare / models.py
:
från django.db importmodeller från django.utils importera tidszon från django.contrib.auth importera get_user_model klass Post (models.Model): author = models.ForeignKey (get_user_model ()) created = models.DateTimeField ('Skapad Datum', standard = timezone.now) title = models.CharField ('Titel', max_length = 200) content = models.TextField ('Innehåll') slug = models.SlugField ('Slug') def __str __ (själv): returnera " "av% s '% (self.title, self.author)
Hooking the Posta
Modellen med Django admin är klar i utgivare / admin.py
filen så här:
från django.contrib import admin från .models import Post @ admin.register (Post) class PostAdmin (admin.ModelAdmin): passera
Slutligen, låt oss haka på utgivare
ansökan med vårt projekt genom att lägga till det till INSTALLED_APPS
lista.
Vi kan nu köra servern och gå vidare till http: // localhost: 8000 / admin /
och skapa våra första inlägg så att vi har något att leka med:
$ ./manage.py körserver
Jag litar på att du har gjort dina läxor och du har skapat inläggen.
Låt oss gå vidare. Nästa uppenbara steg är att skapa ett sätt att se de publicerade inläggen.
# publisher / views.py från django.http import Http404 från django.shortcuts import render från .models import Post def view_post (begäran, slug): försök: post = Post.objects.get (slug = slug) utom Post.DoesNotExist: höja Http404 ("Poll existerar inte") returnera render (begäran, "post.html", context = 'post': post)
Låt oss associera vår nya vy med en webbadress i: quick_publisher / urls.py
# quick_publisher / urls.py från django.conf.urls importera url från django.contrib import admin från publisher.views import view_post urlpatterns = [url (r '^ admin /', admin.site.urls), url (r '^ (? P[a-zA-Z0-9 \ -] +) ', view_post, name = "view_post")]
Slutligen, låt oss skapa mallen som gör inlägget: utgivare / mallar / post.html
Post titel
post.content
Publicerad av post.author.full_name på post.created
Vi kan nu gå vidare till http: // localhost: 8000 / the-slug-of-the-post-you-skapade / i webbläsaren. Det är inte precis ett mirakel av webbdesign, men att göra snygga inlägg ligger utanför ramen för denna handledning.
Här är det klassiska scenariot:
Låt oss lägga till en is_verified
flagga och verification_uuid
på Användare
modell:
# main / models.py import uuid class Användare (AbstractBaseUser, PermissionsMixin): REQUIRED_FIELDS = [] USERNAME_FIELD = 'email'objekt = UserAccountManager () email = models.EmailField (' email ', unique = True, blank = False, null = Falskt) full_name = models.CharField ('fullständigt namn', blank = Sann, null = True, max_length = 400) is_staff = models.BooleanField ('personalstatus', default = False) is_active = models.BooleanField ('aktiv' default = True) is_verified = models.BooleanField ('verifierad', default = False) # Lägg till 'is_verified' flagg verification_uuid = models.UUIDField ('Unik verifiering UUID', default = uuid.uuid4) def get_short_name self.email def get_full_name (själv): returnera self.email def __unicode __ (själv): returnera self.email
Låt oss använda detta tillfälle för att lägga till användarmodellen till admin:
från django.contrib import admin från .models import Användare @ admin.register (User) class UserAdmin (admin.ModelAdmin): passera
Låt oss göra ändringarna reflekterade i databasen:
$ ./manage.py makemigrations $ ./manage.py migrera
Vi behöver nu skriva en kod som skickar ett mail när en användarinstans skapas. Det här är vad Django signaler är för, och det här är ett perfekt tillfälle att röra vid detta ämne.
Signaler avfyras före / efter att vissa händelser inträffar i ansökan. Vi kan definiera återuppringningsfunktioner som utlöses automatiskt när signalerna avfyras. För att göra en återkallningsutlösare måste vi först ansluta den till en signal.
Vi ska skapa en återuppringning som kommer att utlösas efter att en användarmodell har skapats. Vi lägger till den här koden efter Användare
modelldefinition i: main / models.py
från django.db.models importera signaler från django.core.mail import send_mail def user_post_save (avsändare, instans, signal, * args, ** kwargs): om inte instance.is_verified: # Skicka verifierings-email send_mail ('Verifiera ditt QuickPublisher-konto ',' Följ denna länk för att verifiera ditt konto: 'http: // localhost: 8000% s'% reverse ('verifiera', kwargs = 'uuid': str (instance.verification_uuid)), 'från @ quickpublisher. dev ', [instance.email], fail_silently = False,) signals.post_save.connect (user_post_save, avsändare = användare)
Vad vi har gjort här är vi har definierat a user_post_save
funktionen och ansluten den till post_save
signal (en som utlöses efter att en modell har sparats) skickad av Användare
modell.
Django skickar inte bara e-postmeddelanden ut på egen hand. Det måste vara knutet till en e-posttjänst. För enkelhets skull kan du lägga till dina Gmail-uppgifter i quick_publisher / settings.py
, eller du kan lägga till din favoritleverantör av e-post.
Så här ser konfigurationen av Gmail ut:
EMAIL_USE_TLS = True EMAIL_HOST = 'smtp.gmail.com' EMAIL_HOST_USER = '@ gmail.com 'EMAIL_HOST_PASSWORD =' "EMAIL_PORT = 587
För att testa saker, gå in i adminpanelen och skapa en ny användare med en giltig e-postadress som du snabbt kan kolla. Om allt gick bra får du ett mail med en verifieringslänk. Verifieringsrutinen är inte klar än.
Så här kontrollerar du kontot:
# main / views.py från django.http import Http404 från django.shortcuts import render, omdirigera från .models import Användare def home (request): returnera render (request, 'home.html') def verifiera (request, uuid): försök: user = User.objects.get (verification_uuid = uuid, is_verified = False) utom User.DoesNotExist: höja Http404 ("Användaren existerar inte eller är redan verifierad") user.is_verified = Sann user.save () returnera omdirigering () 'Hem')
Haka upp synpunkterna i: quick_publisher / urls.py
# quick_publisher / urls.py från django.conf.urls importera url från django.contrib import admin från publisher.views import view_post från main.views importera hem, verifiera urlpatterns = [url (r '^ $', hem, namn = " hem "), url (r '^ admin /', admin.site.urls), url (r '^ verifiera / (p[a-z0-9 \ -] +) / ', verifiera, namn = "verifiera"), url (r' ^ (P [a-zA-Z0-9 \ -] +) ', view_post, name = "view_post")]
Kom också ihåg att skapa en home.html
fil i main / mallar / home.html
. Det kommer att göras av Hem
se.
Försök att köra hela scenariot igen. Om allt är bra får du ett mail med en giltig verifieringsadress. Om du följer webbadressen och sedan checkar in admin kan du se hur kontot har verifierats.
Här är problemet med vad vi gjort hittills. Du kanske har märkt att det är lite långsamt att skapa en användare. Det beror på att Django skickar verifierings-e-postmeddelandet inuti förfrågningstiden.
Så här fungerar det: Vi skickar användardata till Django-programmet. Applikationen skapar en Användare
modell och skapar sedan en anslutning till Gmail (eller en annan tjänst du valt). Django väntar på svaret, och då returnerar det bara ett svar till vår webbläsare.
Här är var Selleri kommer in. Först, se till att det är installerat:
$ pip installera Selleri
Vi behöver nu skapa ett Selleri-program i vår Django-applikation:
# quick_publisher / celery.py importera os från selleri import Selleri os.environ.setdefault ('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') app = Selleri ('quick_publisher') app.config_from_object ('django.conf: settings') # Ladda uppgiftsmoduler från alla registrerade Django app konfigs. app.autodiscover_tasks ()
Selleri är en uppgiftskö. Den tar emot uppgifter från vår Django-applikation, och den kommer att köra dem i bakgrunden. Selleri måste vara parat med andra tjänster som fungerar som mäklare.
Mäklare mellan sändningen av meddelanden mellan webbapplikationen och Selleri. I den här handledningen använder vi Redis. Redis är lätt att installera, och vi kan enkelt komma igång med det utan för mycket väsen.
Du kan installera Redis genom att följa anvisningarna på Redis Quick Start-sida. Du måste installera Redis Python-biblioteket, pip installera redis
, och bunten som behövs för att använda Redis och selleri: pip installera selleri [redis]
.
Starta Redis-servern i en separat konsol så här: $ redis-server
Låt oss lägga till Celery / Redis relaterade configs till quick_publisher / settings.py
:
# REDIS relaterade inställningar REDIS_HOST = 'localhost' REDIS_PORT = '6379' BROKER_URL = 'redis: //' + REDIS_HOST + ':' + REDIS_PORT + '/ 0' BROKER_TRANSPORT_OPTIONS = 'visibility_timeout': 3600 CELERY_RESULT_BACKEND = 'redis: / / '+ REDIS_HOST +': '+ REDIS_PORT +' / 0 '
Innan allt kan köras i Selleri, måste det förklaras som en uppgift.
Så här gör du det här:
# main / tasks.py import loggning från django.urls importera omvänd från django.core.mail import send_mail från django.contrib.auth import get_user_model från quick_publisher.celery import app @ app.task def send_verification_email (user_id): UserModel = get_user_model ( ) försök: user = UserModel.objects.get (pk = user_id) send_mail ("Verifiera ditt QuickPublisher-konto", "Följ den här länken för att verifiera ditt konto:" http: // localhost: 8000% s '% reverse , kwargs = 'uuid': str (user.verification_uuid)), '[email protected]', [user.email], fail_silently = False,) förutom UserModel.DoesNotExist: logging.warning ("Försökte skicka verifiering email till icke-befintlig användare '% s' "% user_id)
Vad vi har gjort här är det här: Vi flyttade verifieringsadressens funktionalitet i en annan fil som heter tasks.py
.
Några anteckningar:
INSTALLED_APPS
och registrerar uppgifterna i tasks.py
filer.Skicka bekräftelsemail
fungera med @ app.task
. Detta berättar Selleri Detta är en uppgift som kommer att köras i uppgiftskön.användar ID
snarare än a Användare
objekt. Detta beror på att vi kan ha problem med att serialisera komplexa objekt när vi skickar uppgifterna till Selleri. Det är bäst att hålla dem enkla.Kommer tillbaka till main / models.py
, signalkoden blir till:
från django.db.models importera signaler från main.tasks importera send_verification_email def user_post_save (avsändare, instans, signal, * args, ** kwargs): om inte instance.is_verified: # Skicka verifierings-email send_verification_email.delay (instance.pk) signaler .post_save.connect (user_post_save, avsändare = användare)
Lägg märke till hur vi kallar .fördröjning
metod på uppgiftsobjektet. Det betyder att vi skickar upp uppgiften till Selleri och vi väntar inte på resultatet. Om vi använde send_verification_email (instance.pk)
Istället skulle vi fortfarande skicka det till Selleri, men skulle vänta på uppgiften att slutföra, vilket inte är vad vi vill ha.
Innan du börjar skapa en ny användare finns det en fångst. Selleri är en tjänst, och vi måste börja det. Öppna en ny konsol, se till att du aktiverar lämplig virtualenv och navigera till projektmappen.
$ selleriarbetare -A quick_publisher --loglevel = debug --concurrency = 4
Det börjar fyra Celery processarbetare. Ja, nu kan du äntligen gå och skapa en annan användare. Lägg märke till hur det inte finns någon fördröjning, och se till att loggarna i selleri-konsolen visas och se om uppgifterna är korrekt utförda. Det här borde se ut så här:
[2017-04-28 15: 00: 09,190: DEBUG / MainProcess] Uppgift accepterat: main.tasks.send_verification_email [f1f41e1f-ca39-43d2-a37d-9de085dc99de] pid: 62065 [2017-04-28 15: 00: 11,740: INFO / PoolWorker-2] Uppgift main.tasks.send_verification_email [f1f41e1f-ca39-43d2-a37d-9de085dc99de] lyckades i 2.5500912349671125s: Ingen
Här är ett annat vanligt scenario. De flesta mogna webbapplikationer skickar sina användare livscykel e-postmeddelanden för att hålla dem förlovade. Några vanliga exempel på livscykel e-postmeddelanden:
Det här är vad vi ska göra i vår app. Vi ska räkna hur många gånger varje inlägg har blivit sedd och skicka en daglig rapport till författaren. En gång varje dag kommer vi att gå igenom alla användare, hämta sina inlägg och skicka ett mail med ett bord som innehåller inläggen och visa antalet.
Låt oss ändra Posta
modell så att vi kan ta emot scenariot.
klassen Post (models.Model): author = models.ForeignKey (User) created = models.DateTimeField ('Skapad datum', default = timezone.now) title = models.CharField ('Titel', max_length = 200) content = models .TextField ('Innehåll') slug = models.SlugField ('Slug') view_count = models.IntegerField ("Visa antal", standard = 0) def __str __ (själv): returnera "% s" av% s '% self.title, self.author)
Som alltid, när vi ändrar en modell, behöver vi migrera databasen:
$ ./manage.py makemigrations $ ./manage.py migrera
Låt oss också ändra view_post
Django visa för att räkna vyer:
def view_post (request, slug): försök: post = Post.objects.get (slug = slug) utom Post.DoesNotExist: höja Http404 ("Poll existerar inte") post.view_count + = 1 post.save () return render (förfrågan, "post.html", context = 'post': post)
Det skulle vara användbart att visa Visade post.view_count gångerantal visningar
i mallen. Lägg till detta
någonstans inne i utgivare / mallar / post.html
fil. Gör några synpunkter på ett inlägg nu och se hur räknaren ökar.
Låt oss skapa en Selleriuppgift. Eftersom det handlar om inlägg, kommer jag att lägga in det utgivare / tasks.py
:
från django.template import Mall, Context from django.core.mail import send_mail från django.contrib.auth import get_user_model från quick_publisher.celery import app från publisher.models import Post REPORT_TEMPLATE = "" "Så här har du gjort nu: % för inlägg i inlägg% "post.title": visade post.view_count gånger | % endfor% "" "@ app.task def send_view_count_report (): för användare i get_user_model (). objects.all (): posts = Post.objects.filter (författare = användare) om inte inlägg: fortsätt mall = Mall (REPORT_TEMPLATE) send_mail ('Din QuickPublisher Activity', template.render (context = Context ('posts' inlägg)), "[email protected]", [user.email], fail_silently = False,)
Varje gång du ändrar Celery-uppgifterna, kom ihåg att starta om Celery-processen. Selleri måste upptäcka och ladda om uppgifter. Innan du skapar en periodisk uppgift borde vi testa det här ut i Django-skalet för att se till att allt fungerar som tänkt:
$ ./manage.py skal i [1]: från publisher.tasks importera send_view_count_report I [2]: send_view_count_report.delay ()
Förhoppningsvis fick du en snygg liten rapport i din email.
Låt oss nu skapa en periodisk uppgift. Öppna quick_publisher / celery.py
och registrera de periodiska uppgifterna:
# quick_publisher / celery.py import os från selleri import Selleri från selleri.scheman importerar crontab os.environ.setdefault ('DJANGO_SETTINGS_MODULE', 'quick_publisher.settings') app = Selleri ('quick_publisher') app.config_from_object ('django.conf : inställningar ") # Ladda upp uppgiftsmoduler från alla registrerade Django app konfigs. app.autodiscover_tasks () app.conf.beat_schedule = 'send-report-every-single-minute': 'uppgift': 'publisher.tasks.send_view_count_report', 'schema': crontab (), # ändra till 'crontab (minut = 0, timme = 0) 'om du vill att den ska köra dagligen vid midnatt,
Hittills skapade vi ett schema som skulle utföra uppgiften publisher.tasks.send_view_count_report
varje minut som anges av crontab ()
notation. Du kan också ange olika Celery Crontab scheman.
Öppna en annan konsol, aktivera lämplig miljö och starta Celery Beat-tjänsten.
$ selleri -A quick_publisher beat
Beat tjänstens jobb är att driva uppgifter i Selleri enligt schemat. Tänk på att schemat gör det send_view_count_report
Uppgiftskörning varje minut enligt inställningen. Det är bra att testa men inte rekommenderas för en verklig webapplikation.
Uppgifter används ofta för att utföra opålitliga operationer, verksamheter som är beroende av externa resurser eller som lätt kan misslyckas på grund av olika skäl. Här är en riktlinje för att göra dem mer tillförlitliga:
Jag hoppas att det här har varit en intressant handledning för dig och en bra introduktion till Celery med Django.
Här är några slutsatser vi kan dra:
selleri beat
service.