
Teilen Sie:
Tolulope ist ein Software-Ingenieur aus Nigeria. Er liebt es, plattformübergreifende Lösungen und Kommunikationstools für Menschen und Unternehmen zu entwickeln. Wenn er nicht arbeitet, liest er gerne Bücher über Produktdesign, Menschlichkeit und Unternehmertum.
Aufbau eines sozialen CRM mit Django und der Vonage Messages API
Lesedauer: 10 Minuten
In diesem Artikel erfahren Sie, wie Sie die Kernfunktion eines sozialen CRM mit Django und Vonage Messages API erstellen. Unser soziales CRM wird Vertriebsmitarbeitern und dem Kundensupport-Team helfen, mit potenziellen Kunden direkt auf Facebook in Echtzeit zu kommunizieren. Nennen wir es Sales Fox.
Voraussetzungen
Erstellen Sie eine Nachrichtenanwendung über Ihr Vonage-Dashboard. Folgen Sie den Schritten, die hier.
Autorisieren Sie Vonage den Zugriff auf Ihre Facebook-Unternehmensseite und verknüpfen Sie Ihre Anwendung mit Ihrer Facebook-Seite. Folgen Sie den Schritten, die hier.
Redis installieren - Wenn Sie Linux oder Mac verwenden, folgen Sie den Anweisungen hier. Wenn Sie Windows verwenden, folgen Sie bitte den Anweisungen hier.
Installieren Sie Ngrok. Gehen Sie zur Ngrok Download-Seite und folgen Sie den Anweisungen, um Ngrok auf Ihrem Computer einzurichten.
Nachdem Sie nun die Voraussetzungen erfüllt haben, müssen Sie Ihre Entwicklungsumgebung einrichten. Sie müssen Ihre Entwicklungsumgebung für das Lernprogramm einrichten.
Projekt einrichten
Erstellen und Aktivieren Ihrer virtuellen Umgebung Legen Sie ein Verzeichnis für Ihr Projekt an und ändern Sie Ihr Arbeitsverzeichnis in das soeben erstellte Verzeichnis. Führen Sie dann die folgenden Befehle aus, um eine virtuelle Umgebung für Ihr Projekt zu erstellen und zu aktivieren.
python3 -m venv sales-envsource sales-env/bin/activateErforderliche Pakete installieren Um alle benötigten Pakete auf einmal zu installieren, erstellen Sie eine
requirements.txtDatei in dem in Schritt 1 erstellten Verzeichnis. Kopieren Sie den folgenden Codeausschnitt und fügen Sie ihn in Ihrerequirements.txtDatei ein.aioredis==1.3.1 asgiref==3.3.4 async-timeout==3.0.1 attrs==21.2.0 autobahn==21.3.1 Automat==20.2.0 certifi==2021.10.8 cffi==1.14.6 channels==2.4.0 channels-redis==2.4.2 charset-normalizer==2.0.7 constantly==15.1.0 cryptography==3.4.7 daphne==2.5.0 Django==3.2.2 djangorestframework==3.12.4 hiredis==2.0.0 hyperlink==21.0.0 idna==3.2 incremental==21.3.0 msgpack==0.6.2 Pillow==8.2.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.20 pyOpenSSL==20.0.1 python-dotenv==0.19.2 pytz==2021.1 requests==2.26.0 service-identity==21.1.0 six==1.16.0 sqlparse==0.4.1 Twisted==21.7.0 txaio==21.2.1 typing-extensions==3.10.0.0 urllib3==1.26.7 zope.interface==5.4.0KopierenInstallieren Sie nun alle Pakete in
requirements.txtindem Sie den folgenden Befehl in Ihrem Terminal ausführen.pip install -r requirements.txtErstellen Sie Ihr Django-Projekt
Führen Sie
django-admin startproject sales_foxaus, um das Django-Projekt mit dem Namen "sales_fox" zu erstellen.Wir werden zwei Anwendungen in sales_fox erstellen: Die
lead_managerApp zur Verwaltung von Leads und dieconversationApp für Vertriebsmitarbeiter zur Kommunikation mit potenziellen Kunden (den sogenannten Leads). Lassen Sie uns nun unsere beiden Apps erstellen, indem wir diese Befehle ausführen.python manage.py startapp lead_manager python manage.py startapp conversationKopieren
Beachten Sie, dass in diesem Lernprogramm,
Ich werde die Begriffe "Leads" und "Kunden" synonym verwenden. Leads sind potenzielle Kunden, also kann es nicht schaden, sie als Kunden zu betrachten, wo es angebracht ist.
Ich verwende den Begriff
Project Directoryals Bezeichnung für das Verzeichnis, in dem Siesettings.py. Dieses Verzeichnis wurde erstellt, als Sie den Befehldjango-admin startproject sales_fox.Ich verwende den Begriff
Overall Directoryfür das Verzeichnis, das Sie zu Beginn des Tutorials erstellt haben. Es enthält Ihren virtuellen Umgebungsordner, die App-Verzeichnisse und Ihr Projektverzeichnis
Erstellen Sie eine
.envDatei in Ihrem Gesamtverzeichnis. Definieren SieFACEBOOK_ID,VONAGE_API_KEY, undVONAGE_API_SECRET. Ihre .env-Datei sollte wie folgt aussehen:FACEBOOK_ID=YOUR-LINKED-FACEBOOK-ID VONAGE_API_KEY=YOUR-VONAGE-API-KEY VONAGE_API_SECRET=YOUR-VONAGE-API-SECRETKopierenSie finden Ihren Vonage API-Schlüssel und Ihr API-Geheimnis auf Ihrer Vonage Einstellungsseite. Und Ihre Facebook-ID finden Sie auf der Registerkarte
Link social channelsRegisterkarte auf Ihrer Anwendungsseite.Gehen Sie in Ihrem Projektverzeichnis zu
settings.pyund laden Sie die Variablen in Ihrer .env-Datei mitpython-dotenvinstalliert vonrequirements.txt. Fügen Sie das folgende Snippet insettings.pyein, um die .env-Datei zu laden:from dotenv import load_dotenv import os load_dotenv()Kopieren
load_dotenv lädt alle Variablen in unserer .env-Datei als Umgebungsvariable. Definieren Sie nun FACEBOOK_ID, VONAGE_API_KEY, VONAGE_API_SECRET, VONAGE_MESSAGES_ENDPOINT in Ihrer settings.py Datei. Kopieren Sie einfach den unten stehenden Ausschnitt und fügen Sie ihn ein.
FACEBOOK_ID = os.getenv("FACEBOOK_ID")
VONAGE_API_KEY = os.getenv("VONAGE_API_KEY")
VONAGE_API_SECRET = os.getenv("VONAGE_API_SECRET")
VONAGE_MESSAGES_ENDPOINT = "https://api.nexmo.com/v0.1/messages"Unter settings.pyfinden Sie STATIC_URL Variable und addieren Sie die STATICFILES_DIRS und STATIC_FILES unterhalb von STATIC_URLSie sollten dann etwas haben wie:
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'Gehen Sie zu Ihrem Gesamtverzeichnis und erstellen Sie einen Ordner mit dem Namen static. In diesem Ordner werden Sie alle statischen Dateien speichern. Beachten Sie, dass Sie dies nur in einer Entwicklungsumgebung tun sollten. In einer Produktionsumgebung sollten Sie einen externen Speicher wie einen AWS S3-Bucket einrichten, um Ihre statischen Dateien bereitzustellen.
Wir müssen Folgendes hinzufügen channels und die von uns erstellten Anwendungen (lead_manager und conversation) zu INSTALLED_APPS in der Projektdatei settings.py. Ihre INSTALLED_APPS in der Datei settings.py sollte wie folgt aussehen:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'channels',
'lead_manager',
'conversation',
]Django-Kanäle helfen uns, WebSocket-Unterstützung in SalesFox zu integrieren. Eine Channel-Schicht führt die Verwendung von Kanälen und Gruppen in SalesFox ein. Sie hilft uns, verteilte Funktionen in unsere Anwendung zu integrieren. Mehr über Channel-Layer können Sie hier. Für dieses Projekt werde ich Redis als Channel Layer verwenden. Wir haben installiert channels-redis aus requirements.txt installiert. Jetzt fügen wir CHANNEL_LAYER zu hinzufügen. settings.py. Kopieren Sie den unten stehenden Codeausschnitt und fügen Sie ihn ein:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('127.0.0.1', '6379')],
},
},
} Die Messingnägel
Kommen wir nun zur Sache.
Erstellen Sie Modelle für die lead_manager App. Hier werden wir Modelle für Lead und Agent hinzufügen. Das Modell Lead wird Kunden und Interessenten repräsentieren. Das Modell Agent wird die SalesFox-Verkäufer repräsentieren, die mit den Kunden in Kontakt treten werden. Kopieren Sie den folgenden Codeschnipsel und fügen Sie ihn in lead_manager/models.py:
from django.db import models
from django.contrib.auth.models import AbstractUser
#Users are staffs or partners that use the CRM
class User(AbstractUser):
country = models.CharField(max_length=100, blank=True)
address = models.CharField(max_length=200, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
def __str__(self):
return self.username
class Lead(models.Model):
LEAD_SOURCES = (
('organic_search', 'Organic Search'),
('google_ad', 'Google Ad'),
('youtube', 'YouTube'),
('facebook', 'Facebook'),
('instagram', 'Instagram'),
('twitter', 'Twitter'),
)
MEDIA_CHOICES = (
('sms', 'SMS'),
('facebook', 'Facebook'),
('phone_call', 'Phone call')
)
first_name = models.CharField(max_length=25, blank=True)
last_name = models.CharField(max_length=25, blank=True)
age = models.IntegerField(default=0)
facebook_id = models.CharField(max_length=100, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
source = models.CharField(
choices=LEAD_SOURCES,
max_length=50,
blank=True,
help_text="Where Lead found us",
default=LEAD_SOURCES[3][0]
)
preferred_medium = models.CharField(
choices=MEDIA_CHOICES,
max_length=50,
default=MEDIA_CHOICES[1][0],
help_text="Lead's preferred social media for communication"
)
active = models.BooleanField(default=False)
profile_picture = models.ImageField(blank=True, null=True)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
agent = models.ForeignKey("Agent", on_delete=models.SET_NULL, null=True, blank=True, related_name='leads')
def __str__(self):
return self.first_name
@property
def has_agent(self):
return self.agent is not None
class Agent(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='agent')
def __str__(self):
return self.user.usernameWir haben ein Benutzermodell erstellt, um jeden Benutzer in SalesFox zu repräsentieren - das können Community-Manager, Regionalvertreter, Kundenbetreuer usw. sein. Um SalesFox jedoch so schlank wie möglich zu halten, sind die einzigen Benutzer, die wir haben, die Agenten.
Das Lead Modell stellt potenzielle Kunden dar, die sich über ihr Facebook-Konto melden. Das Feld facebook_id stellt die ID des Facebook-Kontos eines Kunden dar. Es ist das Feld, das wir benötigen, damit Agenten eine direkte Nachricht an Kunden auf Facebook senden können. Das Lead Modell hat auch ein preferred_medium Feld. Es enthält das bevorzugte Kommunikationsmittel des Kunden. Wir werden uns nur auf die Kommunikation über Facebook konzentrieren.
`AUTH_USER_MODEL = 'lead_manager.User'`Erstellen wir nun ein Message Modell in der Konversations-App. Das Message Modell repräsentiert eine einzelne Nachricht, die von/an SalesFox gesendet wird. Kopieren Sie den folgenden Codeschnipsel und fügen Sie ihn in models.py der conversation App ein.
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
LEAD_MODEL = models.Q(app_label='lead_manager', model='Lead')
AGENT_MODEL = models.Q(app_label='lead_manager', model='Agent')
communicating_parties = LEAD_MODEL | AGENT_MODEL
class Message(models.Model):
body = models.TextField()
sender_type = models.ForeignKey(
ContentType,
limit_choices_to=communicating_parties,
null=True, blank=True, on_delete=models.SET_NULL, related_name="sent_messages"
)
sender_id = models.PositiveIntegerField(null=True, blank=True, db_index=True)
sender = GenericForeignKey(ct_field='sender_type', fk_field='sender_id')
receiver_type = models.ForeignKey(
ContentType,
limit_choices_to=communicating_parties,
null=True, blank=True, on_delete=models.SET_NULL, related_name="received_messages"
)
receiver_id = models.PositiveIntegerField(null=True, blank=True, db_index=True)
receiver = GenericForeignKey(ct_field='receiver_type', fk_field='receiver_id')
date_created = models.DateTimeField(auto_now_add=True)
message_key = models.CharField(null=True, blank=True, max_length=50)
is_delivered = models.BooleanField(default=False)
def __str__(self):
return "Message (%s) from %s to %s" % (self.id, self.sender, self.receiver)In unserem Message Modell gibt es zwei generische Beziehungen, um den Absender und den Empfänger der Nachricht zu identifizieren. Der Absender und der Empfänger können entweder ein Lead oder ein Agent sein. Das bedeutet, dass nur Agenten oder Leads Nachrichten senden oder empfangen können. Besuchen Sie hier um mehr über generische Relationen in Django zu erfahren.
Erstellen Sie eine Eigenschaftsmethode messages für das Lead Modell in lead_manager/models.py. Diese Methode gibt alle eingehenden und ausgehenden Nachrichten eines Leads zurück.
Fügen Sie in lead_manager/models.py die folgenden Import-Anweisungen ein.
Unter dem Lead Modell die Eigenschaftsmethode "messages", wie im folgenden Ausschnitt dargestellt:
from django.contrib.contenttypes.models import ContentType
from django.db.models import Value
from itertools import chain
@property
def messages(self):
from conversation.models import Message
message_type = ContentType.objects.get_for_model(self)
msgFromLead = Message.objects.filter(sender_id=self.id, sender_type=message_type).annotate(
from_lead=Value(True, models.BooleanField())
)
msgToLead = Message.objects.filter(receiver_id=self.id, receiver_type=message_type).annotate(
from_lead=Value(False, models.BooleanField())
)
messages = sorted(
chain(msgFromLead, msgToLead),
key=lambda instance: instance.date_created
)
return messagesFangen wir mit den Ansichten an.
In lead_manager werden wir Ansichten erstellen, um CRUD-Vorgänge mit dem Lead-Modell durchzuführen. Gehen Sie in den lead_manager-App-Ordner, kopieren Sie den folgenden Code und fügen Sie ihn in views.py ein, um die Ansichten zu erstellen:
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views.generic import TemplateView, ListView, UpdateView, CreateView
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required
from .models import Lead
from .forms import LeadForm
class HomeView(TemplateView):
template_name = 'index.html'
class LeadListView(ListView, LoginRequiredMixin):
template_name = 'lead_manager/lead_list.html'
queryset = Lead.objects.all()
context_object_name = 'leads'
def dispatch(self, request, *args, **kwargs):
if not (request.user.is_superuser and hasattr(request.user, 'agent')):
return PermissionDenied
return super().dispatch(request, *args, **kwargs)
class LeadCreateView(CreateView, LoginRequiredMixin):
template_name = 'lead_manager/lead_create.html'
form_class = LeadForm
def dispatch(self, request, *args, **kwargs):
if not (request.user.is_superuser and hasattr(request.user, 'agent')):
return PermissionDenied
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse('lead_manager:lead_list')
class LeadUpdateView(UpdateView, LoginRequiredMixin):
template_name = 'lead_manager/lead_update.html'
queryset = Lead.objects.all()
form_class = LeadForm
def dispatch(self, request, *args, **kwargs):
if not hasattr(request.user, 'agent'):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
messages.success(self.request, "{}'s info is successfully updated".format(self.get_object()))
return reverse('lead_manager:lead_update', args=[self.get_object().id])
@login_required
def lead_delete(request, pk):
if not request.user.is_superuser:
return PermissionDenied
lead = Lead.objects.only('id').get(id=pk)
lead.delete()
return redirect('lead_manager:lead_list')In den obigen Ansichten überschreiben wir die Dispatch-Methode, um die Berechtigungen für jede Ansicht zu behandeln.
Erstellen Sie forms.py innerhalb des Verzeichnisses der lead_manager-App. Unter forms.pydefinieren Sie LeadForm:
from django import forms
from .models import Lead
class LeadForm(forms.ModelForm):
class Meta:
model = Lead
fields = [
'first_name',
'last_name',
'age',
'facebook_id',
'phone_number',
'source',
'preferred_medium',
'agent'
]Erstellen Sie views.py Datei in einem Unterordner in lead_manager mit dem Namen agent. Und definieren Sie Ihre AgentLoginView und AgentDashboardView Ansichten.
from django.contrib.auth.views import LoginView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.views.generic.base import TemplateView
class AgentLoginView(LoginView):
template_name = 'lead_manager/agent_login.html'
class AgentDashboardView(LoginRequiredMixin, TemplateView):
template_name = 'lead_manager/agent_dashboard.html'
def dispatch(self, request, *args, **kwargs):
if not hasattr(request.user, 'agent'):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
def get(self, request):
assigned_leads = request.user.agent.leads.all()
context = {
'assigned_leads': assigned_leads,
}
return self.render_to_response(context)Wir wollen schaffen lead_manager/urls.py und lead_manager/agent/urls.py.
Wechseln Sie in das Verzeichnis lead_manager und erstellen Sie eine urls.py Datei. Definieren Sie nun URL-Muster für lead_manager-Ansichten.
from django.urls import path
from . import views
app_name = 'lead_manager'
urlpatterns = [
path('', views.LeadListView.as_view(), name='lead_list'),
path('create/', views.LeadCreateView.as_view(), name='lead_create'),
path('<int:pk>/update/', views.LeadUpdateView.as_view(), name='lead_update'),
path('<int:pk>/delete/', views.lead_delete, name='lead_delete'),
]
```
In your lead_manager directory, go to `agent` folder and create a file named `urls.py`. Define URL patterns for agent views as in the snippet below:
```
from django.contrib.auth.views import LogoutView
from django.urls import path
from .views import AgentLoginView, AgentDashboardView
app_name = 'agent'
urlpatterns = [
path('login/', AgentLoginView.as_view(), name='agent_login'),
path('logout/', LogoutView.as_view(), name='agent_logout'),
path('dashboard/', AgentDashboardView.as_view(), name='agent_dashboard'),
]
Anhand der beiden urls.py in der lead_manager-App können Sie bestätigen, dass alle Ansichten, die wir in der lead_manager-App erstellt haben, über entsprechende URL-Konfigurationen verfügen.
Nun müssen wir Django die Login-URL, die Login-Redirect-URL und die Logout-Redirect-URL mitteilen. Fügen Sie folgendes zu settings.py
LOGIN_URL = 'agent:agent_login'
LOGIN_REDIRECT_URL = 'agent:agent_dashboard'
LOGOUT_REDIRECT_URL = 'home'Kommen wir nun zur Konversations-App.
Neben der Konfiguration von Ansichten und URLs werden Sie auch einen Web-Socket-Konsumenten in der Konversations-App einrichten. Dieser ermöglicht die Kommunikation zwischen SalesFox-Agenten und Leads in Echtzeit.
Erstellen wir die lead_conversation_room Ansicht für das Gesprächszimmer. Gehen Sie zu views.py im Gesprächsordner und fügen Sie den folgenden Codeausschnitt ein
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseForbidden
from django.core.exceptions import PermissionDenied
from lead_manager.models import Lead
@login_required
def lead_conversation_room(request, lead_id):
if not hasattr(request.user, 'agent'):
return PermissionDenied
agent = request.user.agent
try:
lead = agent.leads.get(id=lead_id)
except Lead.DoesNotExist:
return HttpResponseForbidden()
context = {"lead": lead}
return render(request, "conversation/room.html", context)Die Ansicht lead_conversation_room bearbeitet die Anfragen von Agenten, die einen Gesprächsraum mit einem Kunden eröffnen wollen.
Erstellen Sie nun send_outbound Funktion. send_outbound Funktion ist verantwortlich für das Senden von Nachrichten von SalesFox an Kunden auf Facebook Messenger. Sie nimmt die zu versendende Nachricht und die Facebook-ID des Kunden als Argumente an.
from django.conf import settings
import requests
import json
import base64
from requests.exceptions import ConnectionError
def send_outbound(message, lead_facebook_id):
url = settings.VONAGE_MESSAGES_ENDPOINT
auth_param = settings.VONAGE_API_KEY + ":" + settings.VONAGE_API_SECRET
auth_code = base64.b64encode(auth_param.encode('utf-8'))
payload = json.dumps({
"from": {
"type": "messenger",
"id": settings.FACEBOOK_ID
},
"to": {
"type": "messenger",
"id": lead_facebook_id
},
"message": {
"content": {
"type": "text",
"text": message
}
}
})
headers = {
'Authorization': 'Basic %s' % auth_code.decode('utf-8'),
'Accept': 'application/json',
'Content-Type': 'application/json'
}
try:
response = requests.request("POST", url, headers=headers, data=payload)
except ConnectionError:
return
return responseDa wir eine Echtzeitkommunikation zwischen Leads und Agenten im Gesprächsraum wünschen, müssen wir einen WebSocket auf der Client-Seite erstellen und einen WebSocket-Konsumenten auf dem Backend einrichten.
Erstellen Sie direkt im Ordner der Konversationsanwendung einen consumers.py Ordner. In dem Ordner consumers.pyerstellen Sie eine WebSocket-Verbraucherklasse - ConversationConsumer.
import json
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
from django.contrib.contenttypes.models import ContentType
from .models import Message
from .views import send_outbound
from lead_manager.models import Lead, Agent
def create_conversation_group(convo_id):
return "conversation_%s" % convo_id
class ConversationConsumer(WebsocketConsumer):
def connect(self):
self.lead_id = self.scope['url_route']['kwargs']['lead_id']
self.conversation = create_conversation_group(self.lead_id)
self.agent = getattr(self.scope['user'], 'agent', None)
try:
self.lead = Lead.objects.get(id=self.lead_id)
except Lead.DoesNotExist:
self.lead = None
# join conversation
async_to_sync(self.channel_layer.group_add)(
self.conversation,
self.channel_name
)
self.accept()
def disconnect(self, exit_code):
# leave conversation
async_to_sync(self.channel_layer.group_discard)(
self.conversation,
self.channel_name
)
def save_message(self, message_data):
if self.agent:
sender_type = ContentType.objects.get_for_model(self.agent)
message_data['sender_type'] = sender_type
message_data['sender_id'] = self.agent.id
if self.lead:
receiver_type = ContentType.objects.get_for_model(self.lead)
message_data['receiver_type'] = receiver_type
message_data['receiver_id'] = self.lead.id
message = Message.objects.create(**message_data)
return message
def receive(self, text_data):
data = json.loads(text_data)
message = data['message']
saved_message = self.save_message({'body': message})
if self.lead:
# send message to Lead on social media (Facebook)
response = send_outbound(message, self.lead.facebook_id)
if response and response.ok:
response_data = response.json()
saved_message.is_delivered = True
saved_message.message_key = response_data["message_uuid"]
saved_message.save()
# send message to everyone connected to the conversation
async_to_sync(self.channel_layer.group_send)(
self.conversation,
{
'type': 'send_to_conversation',
'message': message,
'from_agent': True
}
)
def send_to_conversation(self, event):
# send message to Websocket
self.send(
json.dumps(
{
"message": event['message'],
"from_agent": event['from_agent'],
}
)
)Zur Erklärung der Methoden - Für jeden Agenten, der die Konversationsseite öffnet, gibt es einen Aufruf an ConversationConsumer. Daraus ergibt sich ein neuer Kanal für den Agenten.
connect(): wird aufgerufen, wenn eine WebSocket-Verbindung empfangen wird. Hier fügen wir den Kanal des Agenten zu einer Konversation hinzu und akzeptieren dann die Verbindung.disconnect(): Hier wird der Kanal des Agenten aus dem Gespräch entfernt.receive(): Hier erhalten wir eine neue Nachricht vom Kunden. Danach rufen wirsave_messageauf, der die Nachricht in unserer Datenbank speichert. Anschließend senden wir die Nachricht als Facebook-Direktnachricht an den Lead, indem wirsend_outbound. Die Nachricht wird dann an den Konversationsraum zurückgeschickt. Am Ende derreceiveMethode wird die Nachricht an jeden Agenten im Gesprächsraum gesendet.save_message(): Hier speichern wir die Nachricht des Agenten in der Datenbank. Dies wird aufgerufen inreceivesend_to_conversation(): Wir verwenden dies, um die Nachricht des Agenten an den Gesprächsraum zu senden, so dass jeder Agent im Raum die Nachricht sehen kann.
Richten wir nun das Routing für unsere ConversationConsumer.
Erstellen Sie routing.py im Verzeichnis der Konversationsanwendung und fügen Sie den folgenden Text ein:
from django.urls import re_path
from .consumers import ConversationConsumer
websocket_urlpatterns = [
re_path(r'ws/conversation/(?P<lead_id>\d+)/$', ConversationConsumer),
]Erstellen Sie eine routing.py Datei in Ihrem Projektverzeichnis. Diese Datei enthält die globale Routing-Konfiguration für das Projekt.
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from conversation import routing
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(URLRouter(
routing.websocket_urlpatterns
)),
})Jetzt, Referenz application in settings.py als ASGI-Anwendung, die ausgeführt wird, wenn Sales-Fox über die asynchrone Server-Gateway-Schnittstelle bedient wird:
ASGI_APPLICATION = 'sales_fox.routing.application'Erstellen wir eine inbound Ansicht. Die Ansicht inbound Ansicht empfängt die Nachricht eines Kunden von Vonage, speichert die Nachricht und sendet sie an die Agenten im Gesprächsraum.
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.contrib.contenttypes.models import ContentType
from lead_manager.models import Lead
from .models import Message
@require_POST
@csrf_exempt
def inbound(request):
from .consumers import create_conversation_group
body = json.loads(request.body)
channel_layer = get_channel_layer()
message = body["message"]["content"].get("text")
lead_facebook_id = body["from"]["id"]
lead, _ = Lead.objects.get_or_create(facebook_id=lead_facebook_id)
if message:
sender_type = ContentType.objects.get_for_model(lead)
sender_id = lead.id
message_data = dict(body=message, sender_type=sender_type, sender_id=sender_id)
agent = lead.agent
if agent:
receiver_type = ContentType.objects.get_for_model(agent)
receiver_id = agent.id
message_data["receiver_type"] = receiver_type
message_data["receiver_id"] = receiver_id
message_obj = Message.objects.create(**message_data)
conversation_group = create_conversation_group(lead.id)
try:
async_to_sync(channel_layer.group_send)(
conversation_group,
{
"type": "send_to_conversation",
"message": message,
"from_agent": False,
}
)
message_obj.is_delivered = True
message_obj.save()
except Exception as e:
print("Something went wrong")
print(e)
with open('inbound.txt', 'w') as inbound_file:
json.dump(body, inbound_file, sort_keys=True, indent=2)
return HttpResponse(status=204)Vonage sendet Aktualisierungen des Nachrichtenstatus über den Status-Endpunkt.
Da wir die Statusinformationen in diesem Lehrgang nicht verwenden werden, erstellen wir eine einfache Statusansicht, um den Anfragebody in eine status.txt Datei zu schreiben.
In der Datei views.py Datei der Konversationsanwendung kopieren Sie das Folgende, um die Statusansicht zu erstellen.
@require_POST
@csrf_exempt
def status(request):
body = json.loads(request.body)
with open('status.txt', 'w') as status_file:
json.dump(body, status_file)
return HttpResponse(status=204)Lassen Sie uns URL-Konfigurationen für die Konversations-App erstellen. Gehen Sie in das Verzeichnis der Konversations-App und erstellen Sie die urls.py Datei. Kopieren Sie dann das unten stehende Codeschnipsel und fügen Sie es ein:
from django.urls import path
from .views import inbound, status, lead_conversation_room
app_name = 'conversation'
urlpatterns = [
path('inbound/', inbound, name='conversation-inbound'),
path('status/', status, name='conversation-status'),
path('lead/<int:lead_id>/', lead_conversation_room, name="lead-conversation-room"),
]Gehen Sie in das Projektverzeichnis und suchen Sie die Datei urls.py. Diese Datei befindet sich in demselben Verzeichnis wie settings.py. Kopieren Sie nun den folgenden Code und fügen Sie ihn ein:
from django.urls import path
from .views import inbound, status, lead_conversation_room
app_name = 'conversation'
urlpatterns = [
path('inbound/', inbound, name='conversation-inbound'),
path('status/', status, name='conversation-status'),
path('lead/<int:lead_id>/', lead_conversation_room, name="lead-conversation-room"),
]Nun, da wir mit dem Backend unseres Projekts fertig sind. Lassen Sie uns die Frontend-Dateien erstellen.
Gehen Sie zu Ihrem statischen Ordner im Gesamtverzeichnis und erstellen Sie einen Ordner namens css. Im Ordner css Ordner erstellen Sie zwei Dateien style.css und chat.css.
In styles.csskopieren Sie die folgenden Stile und fügen Sie sie ein
.container {
margin: 30px;
}
.link-group {
display: inline-flex;
column-gap: 20px;
}
a {
text-decoration: none;
}
.list {
margin-bottom: 20px;
}Kopieren Sie in chat.css die folgenden Stile und fügen Sie sie ein:
.container {
max-width: 500 !important;
margin: auto;
margin-top: 4%;
letter-spacing: 0.5px;
}
.msg-header {
border: 1px solid #ccc;
width: 100%;
height: 10%;
border-bottom: none;
display: inline-block;
background-color: #007bff;
}
.active {
width: 120px;
float: left;
margin-top: 10px;
}
.active h4 {
font-size: 20px;
margin-left: 10px;
color: #fff;
}
.msg-inbox {
border: 1px solid #ccc;
overflow: hidden;
padding-bottom: 20px;
}
.chats {
padding: 30px 15px 0 25px;
}
.msg-page {
height: 400px;
overflow-y: auto;
}
.received-msg {
display: inline-block;
padding: 0 0 0 10px;
vertical-align: top;
width: 53%;
}
.received-msg p {
background: #efefef none repeat scroll;
border-radius: 10px;
color: #646464;
font-size: 14px;
margin: 0;
padding: 5px 10px 5px 12px;
width: 100%;
}
.time {
color: #777;
display: block;
font-size: 12px;
margin: 8px 0 0;
}
.outgoing-msg {
float: left;
width: 46%;
margin-left: 45%;
}
.outgoing-msg p {
background: #007bff none repeat scroll 0 0;
color: #fff;
border-radius: 10px;
font-size: 14px;
margin: 0;
padding: 5px 10px 5px 12px;
width: 100%;
}
.msg-bottom {
position: relative;
width: 100%;
height: 20%;
background: #007bff;
display: inline-block;
}
.input-group {
float: right;
margin: 10px 20px 10px 0;
outline: none !important;
border-radius: 20px;
width: 61% !important;
background-color: #fff;
}
.form-control {
border: none !important;
border-radius: 20px !important;
}
.input-group-text {
background: transparent !important;
border: none !important;
color: #007bff;
cursor: pointer;
}
.input-group-append {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.input-group .fa {
color: #007bff;
float: right;
}
.bottom-icons {
float: left;
margin-top: 17ox;
width: 30px !important;
margin-left: 22px;
}
.bottom-icons .fa {
color: #007bff;
padding: 5px;
}
.form-control:focus {
border-color: none !important;
box-shadow: none !important;
}Wir werden die Datei chat.css für die Konversation verwenden room.html verwenden, während wir für die anderen Seiten die Datei styles.css benutzen. Erstellen Sie nun in Ihrem Gesamtverzeichnis einen Ordner namens templates und erstellen Sie zwei HTML-Dateien - base.html und index.html. Sie werden erweitern base.html in jeder anderen HTML-Datei außer in der Unterhaltung room.html.
Kopieren Sie in base.html den folgenden Text und fügen Sie ihn ein
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<title>{% block title %} Sales Fox {% endblock title %}</title>
</head>
<body>
<div class="container">
{% block content %}
{% endblock content %}
</div>
</body>
{% block script %}
{% endblock script %}
</html>
Kopieren Sie in index.html (Startseite) den folgenden Text und fügen Sie ihn ein
{% extends 'base.html' %}
{% load static %}
{% block content %}Wechseln Sie nun in das Verzeichnis der Anwendung lead_manager. Erstellen Sie einen Ordner templates und in templateseinen weiteren Ordner an lead_manager. Erstellen Sie in lead_manager/templates/lead_manager fünf html-Dateien - lead_list.html, lead_create.html, lead_update.html, agent_login.html, agent_dashboard.html.
lead_list.html,
{% extends 'base.html' %}
{% load static %}
{% block content %}
<a href="{% url 'agent:agent_dashboard' %}">Go to dashboard</a>
<h4>List of leads</h4>
<ul>
{% for lead in leads %}
<li class="list">
<div class="link-group">
<div style="width: 100px;">
{{lead.first_name}} ({{lead.id}})
</div>
<a href="{% url 'lead_manager:lead_update' lead.id %}">Update</a>
<a href="{% url 'lead_manager:lead_delete' lead.id %}"> Delete</a>
{% if not lead.has_agent %} | <span style="color: red;">Not Assigned</span> {% endif %}
</div>
</li>
{% empty %}
<p>Lead list is empty</p>
{% endfor %}
</ul>
<div class="create_lead_link">
<a href="{% url 'lead_manager:lead_create' %}">Create new lead</a>
</div>
{% endblock content %}lead_create.html,
{% extends 'base.html' %}
{% load static %}
{% block content %}
<a href="{% url 'lead_manager:lead_list' %}">Go to lead list</a>
<h1>Lead Creation Form</h1>
<form action="." method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send">
</form>
{% endblock content %}lead_update.html,
{% extends 'base.html' %}
{% load static %}
{% block content %}
<a href="{% url 'agent:agent_dashboard' %}">Go to dashboard</a>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<form action="." method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Send">
</form>
{% endblock content %}
agent_login.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<h1>Login to your dashboard</h1>
<form action="." method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Login">
</form>
{% endblock content %}
agent_dashboard.html
{% extends 'base.html' %}
{% load static %}
{% block content %}
<h4>List of leads assigned to you</h4>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if="" message.tags="" %}="" class="message-{{ message.tags }}" {%="" endif="">{{ message }}
{% endfor %}
</li{%></ul>
{% endif %}
<div>
<ul>
{% for lead in assigned_leads %}
<li class="list">
<div class="link-group">
<div style="width: 100px;">
{{lead.first_name}} ({{lead.id}})
</div>
<a href="{% url 'lead_manager:lead_update' lead.id %}">Update</a>
<a href="{% url 'conversation:lead-conversation-room' lead.id %}">Go to conversation room</a>
</div>
</li>
{% empty %}
<p>No assigned lead</p>
{% endfor %}
</ul>
</div>
<div class="link-group">
{% if request.user.is_superuser %}
<a href="{% url 'lead_manager:lead_list' %}">View lead list</a>
<a href="{% url 'lead_manager:lead_create' %}">Create new lead</a>
{% endif %}
<a href="{% url 'agent:agent_logout' %}">Logout</a>
</div>
{% endblock content %}Erstellen Sie im Verzeichnis der Konversationsanwendung einen Ordner templates und im templates Ordner einen Unterordner conversation.
Erstellen Sie im Ordner "conversation/templates/conversation" eine room.html Datei. Kopieren Sie den folgenden Text und fügen Sie ihn ein:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{% static 'css/chat.css' %}">
<title>Conversation with {{lead}}</title>
</head>
<body>
<div class="container">
<a href="{% url 'agent:agent_dashboard' %}">Go to Dashboard</a>
<div class="msg-header">
<div class="active">
<h4>{{lead.first_name}} {{lead.last_name}}</h4>
</div>
</div>
<div class="conversation">
<div class="msg-inbox">
<div class="chats">
<div class="msg-page" id="msgPage">
{% for message in lead.messages %}
{% if message.from_lead %}
<div class="received-msg">
<div class="received-msg-inbox">
<p>{{message.body}} {{message.from_lead}}</p>
<span class="time">{{message.date_created}}</span>
</div>
</div>
{% else %}
<div class="outgoing-msg">
<p>{{message.body}}</p>
<span class="time">{{message.date_created}}</span>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<div class="msg-bottom">
<div class="input-group">
<textarea name="message" id="msgWriter" rows="3" class="form-control"></textarea>
<div class="input-group-append">
<span class="input-group-text" id="send">Send</span>
</div>
</div>
</div>
</div>
</div>
<script>
const selectElement = (e) => document.querySelector(e);
let messagePage = selectElement("#msgPage");
let msgWriter = selectElement("#msgWriter");
const msgType = {agent: 'outgoing', lead: 'received'}
let socket = null
const keepScrollToEnd = () => {
messagePage.scrollTop = messagePage.scrollHeight
}
const getMessageBox = (text, date, type=msgType.agent) => {
const parentDiv = document.createElement('div')
parentDiv.classList.add(`${type}-chats`)
const childDiv = document.createElement('div')
childDiv.classList.add(`${type}-msg`)
const msgParagraph = document.createElement('p')
msgParagraph.textContent = text
const dateSpan = document.createElement('span')
dateSpan.classList.add('time')
dateSpan.textContent = date
childDiv.append(msgParagraph, dateSpan)
parentDiv.append(childDiv)
return parentDiv
}
// Displays new message in messagePage
const showNewMessage = (val) => {
let msgElement
if (val.from_agent){
msgElement = getMessageBox(text=val.message, date=val.date, type=msgType.agent)
} else {
msgElement = getMessageBox(text=val.message, date=val.date, type=msgType.lead)
}
messagePage.append(msgElement);
keepScrollToEnd()
}
function sendMessage(event) {
if (!msgWriter.value) return false;
if (!socket) {
alert("No socket connection. Reload browser");
return false
}
socket.send(JSON.stringify({"message": msgWriter.value}));
msgWriter.value = "";
event.preventDefault();
return false
}
if (!window["WebSocket"]) {
alert("Your browser does not support web sockets. Change browser");
} else {
var conversationURL = "ws://" + window.location.host + "/ws/conversation/" + "{{ lead.id }}/"
socket = new WebSocket(conversationURL);
socket.onclose = function(){
alert("Web socket connection has been closed");
}
// calls showNewMessage if socket receives message
socket.onmessage = function(msg) {
showNewMessage(JSON.parse(msg.data));
}
}
selectElement("#send").addEventListener("click", sendMessage, false);
</script>
</body>
</htmlVor dem schließenden Tag für das body-Element in room.html befindet sich ein Skript, das die WebSocket-Operation und das Rendering der Nachrichten im Konversationsraum übernimmt.
SalesFox zum Laufen bringen
Wir haben die Entwicklung von SalesFox jetzt abgeschlossen.
Befolgen Sie diese Schritte, um SalesFox lokal zu starten.
Führen Sie aus.
redis-serverum Redis zu starten. Sie können den Redis-Server sicher anhalten, indem Sieredis-cli shutdownErstellen Sie einen HTTP-Tunnel mit Ngrok, der die Anfrage an den Port weiterleitet, von dem aus Sie SalesFox ausführen. Dadurch erhalten Sie eine öffentlich zugängliche URL für Ihren SalesFox
localhost:port. Mehr darüber erfahren Sie hier.Gehen Sie zu der .env Datei in Ihrem Gesamtverzeichnis. Definieren Sie eine neue env-Variable namens HOST, die auf Ihre Ngrok-Tunnel-URL gesetzt ist.
HOST=4339-197-210-53-35.ngrok.iohinzufügen
HOSTaus der .env-Datei zuALLOWED_HOSTinsettings.py.ALLOWED_HOSTDefinition insettings.pysollte wie folgt aussehen:ALLOWED_HOSTS = [os.getenv('HOST'), "localhost", "127.0.0.1"]Erinnern Sie sich, dass wir in unserer Vonage-Anwendungsseite Dummy-URLs als Eingangs- und Status-URLs eingegeben haben. Jetzt werden wir diese URLs durch die richtigen Werte ersetzen. Da mein Tunnel-Host
http://4339-197-210-53-35.ngrok.iolautet, wird meine Eingangs-URLhttp://4339-197-210-53-35.ngrok.io/conversation/inboundund meine Status-URL lautethttp://4339-197-210-53-35.ngrok.io/conversation/status.Gehen Sie auf Ihre Vonage-Anwendungsseite und aktualisieren Sie die
Inbound URLundStatus URLFelder.Gehen Sie nun zu Ihrem Terminal (stellen Sie sicher, dass Sie sich im Gesamtverzeichnis befinden). Führen Sie dann
python manage.py runserverum SalesFox auf Port 8000 zu bedienen.python manage.py runserver 9000.
Schlussfolgerung
Wenn Sie hier angekommen sind, danke, dass Sie dieses Projekt mit mir aufgebaut haben. Im Laufe des Aufbaus SalesFox, haben wir auf das Minimum möglich Funktionen und Design gehalten. Sie können jedoch so viel mehr tun, indem Sie mehr Funktionen auf SalesFox erstellen.
Sie können weitere preferred_medium-Optionen für Leads hinzufügen. Vonage bietet eine Vielzahl von Kommunikations-APIs an, von denen Sie einige für SalesFox entwickeln können. Es lohnt sich, diese zu überprüfen hier.
Zum Wohl!
Teilen Sie:
Tolulope ist ein Software-Ingenieur aus Nigeria. Er liebt es, plattformübergreifende Lösungen und Kommunikationstools für Menschen und Unternehmen zu entwickeln. Wenn er nicht arbeitet, liest er gerne Bücher über Produktdesign, Menschlichkeit und Unternehmertum.