One place for hosting & domains

      How To Create a Laravel Contact Form and Send Emails with SendGrid


      The author selected the Open Source Initiative to receive a donation as part of the Write for DOnations program.

      Introduction

      Laravel is a free, open source PHP framework, based on Symfony and used for creating web applications. SendGrid is a cloud-based SMTP provider that allows you to send email without having to maintain email servers.

      Having a contact form on your website makes it easier for your visitors to contact you directly. For your contact form to work correctly and send out emails, you need an SMTP server. This tutorial will use SendGrid and their free SMTP service to deliver the emails sent out from the website contact form to an email inbox.

      In this tutorial, you’ll add a contact form to an existing Laravel application and configure it to send emails via SMTP using SendGrid.

      Prerequisites

      If you don’t already have a Laravel application set up, you will need the following:

      After you have set up your Laravel application, you’ll also need the following:

      • A SendGrid account. You can visit the SendGrid registration page to sign up for a free SendGrid account.
      • A fully registered domain name pointed to your server. This tutorial will use your_domain throughout. You can purchase a domain name on Namecheap, get one for free on Freenom, or use the domain registrar of your choice. For DigitalOcean, you can follow this introduction to DigitalOcean DNS for details on how to add them.

      Step 1 — Creating the Sender Identity

      SendGrid requires you to verify the ownership of your domain name before allowing you to start sending emails. In order to verify your domain name, go to your SendGrid account, then go to the Dashboard and click on Authenticate your Domain.

      This will take you to a page where you will need to specify your DNS host and choose if you would like to brand the links for your domain. Email link branding allows you to set all of the links used for click-tracking in your emails to your domain instead of from sendgrid.net.

      SendGrid choose DNS host

      Then click Next and on the next page, specify your domain name.

      SendGrid specify your domain name

      Finally, you will need to add the DNS records provided by SendGrid to complete their verification process. For more information on how to manage your DNS records, you can read this tutorial on How to Create, Edit, and Delete DNS Records.

      SendGrid verify DNS records

      Once you have added the DNS records to your DNS zone, go back to SendGrid and hit the Verify button.

      With your SendGrid Identity verified, you need to generate an API key, which you will use in your Laravel .env file.

      From the menu on the left, click on API Keys and then click on the Create API Key button. For security, set the API Key Permissions to Restricted Access.

      SendGrid Create API Key

      After that, scroll down and add the Mail Send permissions.

      SendGrid send mail permissions

      Finally, click on the Create & View button to get your API key. The API key will only be visible once, so be sure to take note of it in a secure place.

      Now that you’ve configured your domain with SendGrid and generated your API key, you’ll configure the SMTP details for your Laravel application.

      Step 2 — Configuring the SMTP Details

      The .env file in Laravel is used to store various configuration options for your application environment. Since there is usually some sensitive information in the .env file, like your database connection details, you should not commit the .env file to source control.

      If you completed the prerequisite tutorial, you’ll need to be in the /var/www/travellist directory to access your .env file:

      After that, use your favorite text editor open the .env file:

      There are many configuration variables in the .env file—in this tutorial you’ll only change the MAIL variables.

      To do so, find the MAIL_ settings and configure the variables as following, adding in your copied API key to sendgrid_api_key and updating the other highlighted fields as necessary:

      .env

      . . .
      MAIL_MAILER=smtp
      MAIL_HOST=smtp.sendgrid.net
      MAIL_PORT=587
      MAIL_USERNAME=apikey
      MAIL_PASSWORD=sendgrid_api_key
      MAIL_ENCRYPTION=tls
      . . .
      

      The following list contains an overview of the variables that have to be updated in order for your Laravel application to start using the SendGrid SMTP server:

      • MAIL_HOST: The SendGrid SMTP hostname, which will be used for sending out emails.
      • MAIL_PORT: The SendGrid secure TLS SMTP port.
      • MAIL_USERNAME: Your SendGrid username. By default, it is apikey for all accounts.
      • MAIL_PASSWORD: Your SendGrid API Key.
      • MAIL_ENCRYPTION: The mail encryption protocol. In this case you will use TLS as it secures the email content during the transfer between the servers.

      Save and exit the file.

      With your SMTP settings in place, you are ready to proceed and configure your contact controller.

      Step 3 — Creating the Controller

      Next you’ll create a controller that will handle your POST and GET requests for your contact form page.

      You’ll use the GET route to return the HTML page containing your contact form, and the POST route will handle the contact form submissions.

      In order to create a controller called ContactController in Laravel, use the following artisan command:

      • php artisan make:controller ContactController

      After running the command, you will get the following output:

      Output

      Controller created successfully.

      This command will create a new controller at app/Http/Controllers/ContactController.php.

      Run the following to edit the ContactController.php file:

      • nano app/Http/Controllers/ContactController.php

      First, you’ll include the Laravel Mail facade so that you can use the mail functionality in your new controller. A facade in Laravel is a class that provides access to different Laravel features. For more information about Laravel facades, take a look at the official Laravel Facades documentation.

      To include the Laravel Mail facade add the following:

      app/Http/Controllers/ContactController.php

      <?php
      
      namespace AppHttpControllers;
      
      use IlluminateHttpRequest;
      use Mail;
      . . .
      

      Then add the method that will handle your GET requests and return the contact page view:

      app/Http/Controllers/ContactController.php

      . . .
      class ContactController extends Controller
      {
              public function contact(){
                      return view('contact');
              }
      }
      

      Finally, let’s add a method that will handle the POST requests and send out the emails:

      app/Http/Controllers/ContactController.php

      ...
      class ContactController extends Controller
      {
              public function contact(){
                      return view('contact');
              }
      
          public function contactPost(Request $request){
              $this->validate($request, [
                              'name' => 'required',
                              'email' => 'required|email',
                              'comment' => 'required'
                      ]);
      
              Mail::send('email', [
                      'name' => $request->get('name'),
                      'email' => $request->get('email'),
                      'comment' => $request->get('comment') ],
                      function ($message) {
                              $message->from('youremail@your_domain');
                              $message->to('youremail@your_domain', 'Your Name')
                              ->subject('Your Website Contact Form');
              });
      
              return back()->with('success', 'Thanks for contacting me, I will get back to you soon!');
      
          }
      }
      

      Within the highlighted lines, you’ll need to change some of the variables, like so:

      • $message->from('youremail@your_domain');: Change the youremail@your_domain with your actual email address.

      • $message->to('youremail@your_domain', 'Your Name'): The $message->to and the $message->from do not necessarily need to match. You can also change the $message->to value with another email address to which you would like to receive all of your contact form inquiries.

      • subject('Your Website Contact Form');: You can also change the email subject by editing the message inside the subject method.

      Note: the $message->from('youremail@your_domain'); address needs to match the domain name that you used with SendGrid.

      Once you’ve finished these edits, the following will be your full ContactController.php file:

      app/Http/Controllers/ContactController.php

      <?php
      
      namespace AppHttpControllers;
      
      use IlluminateHttpRequest;
      use Mail;
      
      class ContactController extends Controller
      {
          public function contact(){
              return view('contact');
          }
      
          public function contactPost(Request $request){
              $this->validate($request, [
                              'name' => 'required',
                              'email' => 'required|email',
                              'comment' => 'required'
                      ]);
      
              Mail::send('email', [
                      'name' => $request->get('name'),
                      'email' => $request->get('email'),
                      'comment' => $request->get('comment') ],
                      function ($message) {
                              $message->from('youremail@your_domain');
                              $message->to('youremail@your_domain', 'Your Name')
                                      ->subject('Your Website Contact Form');
              });
      
              return back()->with('success', 'Thanks for contacting me, I will get back to you soon!');
      
          }
      }
      

      Save and exit your file once you’ve finished your edits.

      Your Contact Controller has two methods:

      • contact(): This method returns your contact Blade view template, which will hold your HTML page that has the HTML layout for your contact form. Blade is the templating engine that comes with Laravel. In your Blade template views, you can add your HTML structure along with PHP code and Blade syntax.
      • contactPost(): This method handles all of the contact form submissions—where you handle the input validation and send out the emails.

      You handle the validation inside the contactPost() method with the $this->validate() method. Inside the validation method, you specify that the name, email, and comment are required. That way, your users will not be able to submit empty or incomplete contact form inquiries. For more information on how the Laravel validation works, take a look at the official Laravel Validation documentation.

      When validation is successful, the Mail::send() method constructs your email body and subject and then sends out the email.

      Finally, if the email was sent successfully, you return a success message that displays to your users.

      You’ve set up your contact controller and can now move on to GET and POST routes.

      Step 4 — Creating the Routes

      Laravel routes allow you to create SEO-friendly URLs for your application. By using Laravel routes, you can route your application requests to specific controllers, where you handle your application logic.

      You’ll create two routes in your routes/web.php file to use the methods you set up in the previous step.

      You will first create a GET route that maps to your contact method in your ContactController. This method only returns your contact Blade view. Open routes/web.php with the following command:

      Add the GET route at the bottom of the file:

      Note: If you followed the prerequisites, you’ll have different content in you routes/web.php file. You can add your routes to the end of this file as per the instructions in this tutorial.

      routes/web.php

      <?php
      use IlluminateSupportFacadesRoute;
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      
      Route::get('/contact', 'ContactController@contact')->name('contact');
      

      You’ll now add a POST route and map it to your contactPost method, which will handle your user contact form submissions:

      routes/web.php

      <?php
      use IlluminateSupportFacadesRoute;
      
      /*
      |--------------------------------------------------------------------------
      | Web Routes
      |--------------------------------------------------------------------------
      |
      | Here is where you can register web routes for your application. These
      | routes are loaded by the RouteServiceProvider within a group which
      | contains the "web" middleware group. Now create something great!
      |
      */
      Route::get('/contact', 'ContactController@contact')->name('contact');
      Route::post('/contact', 'ContactController@contactPost')->name('contactPost');
      

      Once you have your Controller and Route ready, you can save and exit your file then proceed to the next step where you will prepare your Blade views.

      Step 5 — Creating the Blade Views

      In this step you will start by creating a view in the application that will hold your HTML contact form. It will have three input fields:

      • Input field with type text for the email address of the user
      • Input field with type text for the name of the user
      • Text area for the comment

      Create a file called resources/views/contact.blade.php:

      • nano resources/views/contact.blade.php

      Then add the following content:

      resources/views/contact.blade.php

      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Contact Form with Laravel and SendGrid</title>
          <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
              integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
      </head>
      
      <body>
      
          <div class="container">
              @if(session('success'))
              <div class="alert alert-success">
                {{ session('success') }}
              </div>
              @endif
      
              <form method="POST" action="/contact">
                  @csrf
                  <div class="form-group {{ $errors->has('name') ? 'has-error' : '' }}">
                      <label for="email">Email address</label>
                      <input name="email" type="email" class="form-control" id="email" aria-describedby="emailHelp"
                          placeholder="Enter your email">
                      <span class="text-danger">{{ $errors->first('email') }}</span>
                  </div>
                  <div class="form-group {{ $errors->has('name') ? 'has-error' : '' }}">
                      <label for="name">Name</label>
                      <input name="name" type="text" class="form-control" id="name" aria-describedby="name" placeholder="Your name">
                      <span class="text-danger">{{ $errors->first('name') }}</span>
      
                  </div>
                  <div class="form-group {{ $errors->has('name') ? 'has-error' : '' }}">
                      <label for="exampleInputPassword1">Comment</label>
                      <textarea name="comment" class="form-control" id="exampleFormControlTextarea1" rows="3"></textarea>
                      <span class="text-danger">{{ $errors->first('comment') }}</span>
                  </div>
                  <button type="submit" class="btn btn-primary">Submit</button>
              </form>
          </div>
      
      </body>
      
      </html>
      

      This is an HTML form with a POST method to the /contact route. When someone fills out the contact form, it’ll be handled by your contactPost method.

      The <link> tag inside the <head> tag is used to include Bootstrap. You’re using some styling for the HTML form. You can change the style of the form so that it matches the design of your website. For more information on how to style your website, you can take a look at our CSS resources page.

      The form is wrapped up in different <div> tags with classes from Bootstrap. You’re using the <div> tags to create the structure of the contact form. For more information on how the <div> tags work, check out the How To Use a <div>, the HTML Content Division Element tutorial.

      Save and exit this file.

      The next view that you’ll create is your email view.

      Open the resources/views/email.blade.php file:

      • nano resources/views/email.blade.php

      Then add the following content:

      resources/views/email.blade.php

      Inquiry from: {{ $name }}
      <p> Email: {{ $email }} </p>
      <p> Message: {{ $comment }} </p>
      

      This contains the email content that will be sent to users that complete your contact form. Save and exit the file.

      With the styling and views complete, you’re ready to go ahead and test the contact form.

      To test the contact form, visit http://your_domain/contact via your browser.

      You’ll see the Bootstrap HTML form that you created in the previous step.

      Complete all of the required fields and hit the Submit button. You will receive a green notification that the message was sent successfully.

      Laravel contact form message

      You can test the form by submitting it without filling any of the fields. The validation that you added in your controller will catch that and it’ll inform you that the fields must not be empty.

      Laravel contact form validation

      Finally, you can check your email account and make sure that you’ve received the test email and you can see it in your inbox.

      Conclusion

      You have now successfully added a contact form to your existing Laravel website.

      You can also find more information in the official Laravel documentation.

      To make sure that your contact form is secure, you can install an SSL certificate for your website by following our guide on How To Secure Nginx with Let’s Encrypt.

      To learn more about Laravel, check out our collection of Laravel resources.



      Source link

      How To Send Web Push Notifications from Django Applications


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      The web is constantly evolving, and it can now achieve functionalities that were formerly only available on native mobile devices. The introduction of JavaScript service workers gave the web newfound abilities to do things like background syncing, offline caching, and sending push notifications.

      Push notifications allow users to opt-in to receive updates to mobile and web applications. They also enable users to re-engage with existing applications using customized and relevant content.

      In this tutorial, you’ll set up a Django application on Ubuntu 18.04 that sends push notifications whenever there’s an activity that requires the user to visit the application. To create these notifications, you will use the Django-Webpush package and set up and register a service worker to display notifications to the client. The working application with notifications will look like this:

      Web push final

      Prerequisites

      Before you begin this guide you’ll need the following:

      Step 1 — Installing Django-Webpush and Getting Vapid Keys

      Django-Webpush is a package that enables developers to integrate and send web push notifications in Django applications. We’ll use this package to trigger and send push notifications from our application. In this step, you will install Django-Webpush and obtain the Voluntary Application Server Identification (VAPID) keys that are necessary for identifying your server and ensuring the uniqueness of each request.

      Make sure you are in the ~/djangopush project directory that you created in the prerequisites:

      Activate your virtual environment:

      • source my_env/bin/activate

      Upgrade your version of pip to ensure it's up-to-date:

      • pip install --upgrade pip

      Install Django-Webpush:

      • pip install django-webpush

      After installing the package, add it to the list of applications in your settings.py file. First open settings.py:

      • nano ~/djangopush/djangopush/settings.py

      Add webpush to the list of INSTALLED_APPS:

      ~/djangopush/djangopush/settings.py

      ...
      
      INSTALLED_APPS = [
          ...,
          'webpush',
      ]
      ...
      

      Save the file and exit your editor.

      Run migrations on the application to apply the changes you've made to your database schema:

      The output will look like this, indicating a successful migration:

      Output

      Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, webpush Running migrations: Applying webpush.0001_initial... OK

      The next step in setting up web push notifications is getting VAPID keys. These keys identify the application server and can be used to reduce the secrecy for push subscription URLs, since they restrict subscriptions to a specific server.

      To obtain VAPID keys, navigate to the wep-push-codelab web application. Here, you'll be given automatically generated keys. Copy the private and public keys.

      Next, create a new entry in settings.py for your VAPID information. First, open the file:

      • nano ~/djangopush/djangopush/settings.py

      Next, add a new directive called WEBPUSH_SETTINGS with your VAPID public and private keys and your email below AUTH_PASSWORD_VALIDATORS:

      ~/djangopush/djangopush/settings.py

      ...
      
      AUTH_PASSWORD_VALIDATORS = [
          ...
      ]
      
      WEBPUSH_SETTINGS = {
         "VAPID_PUBLIC_KEY": "your_vapid_public_key",
         "VAPID_PRIVATE_KEY": "your_vapid_private_key",
         "VAPID_ADMIN_EMAIL": "[email protected]"
      }
      
      # Internationalization
      # https://docs.djangoproject.com/en/2.0/topics/i18n/
      
      ...
      

      Don't forget to replace the placeholder values your_vapid_public_key, your_vapid_private_key, and [email protected] with your own information. Your email address is how you will be notified if the push server experiences any issues.

      Next, we'll set up views that will display the application's home page and trigger push notifications to subscribed users.

      Step 2 — Setting Up Views

      In this step, we'll setup a basic home view with the HttpResponse response object for our home page, along with a send_push view. Views are functions that return response objects from web requests. The send_push view will use the Django-Webpush library to send push notifications that contain the data entered by a user on the home page.

      Navigate to the ~/djangopush/djangopush folder:

      • cd ~/djangopush/djangopush

      Running ls inside the folder will show you the project's main files:

      Output

      /__init__.py /settings.py /urls.py /wsgi.py

      The files in this folder are auto-generated by the django-admin utility that you used to create your project in the prerequisites. The settings.py file contains project-wide configurations like installed applications and the static root folder. The urls.py file contains the URL configurations for the project. This is where you will set up routes to match your created views.

      Create a new file inside the ~/djangopush/djangopush directory called views.py, which will contain the views for your project:

      • nano ~/djangopush/djangopush/views.py

      The first view we'll make is the home view, which will display the home page where users can send push notifications. Add the following code to the file:

      ~/djangopush/djangopush/views.py

      from django.http.response import HttpResponse
      from django.views.decorators.http import require_GET
      
      @require_GET
      def home(request):
          return HttpResponse('<h1>Home Page<h1>')
      

      The home view is decorated by the require_GET decorator, which restricts the view to GET requests only. A view typically returns a response for every request made to it. This view returns a simple HTML tag as a response.

      The next view we'll create is send_push, which will handle sent push notifications using the django-webpush package. It will be restricted to POST requests only and will be exempted from Cross Site Request Forgery (CSRF) protection. Doing this will allow you to test the view using Postman or any other RESTful service. In production, however, you should remove this decorator to avoid leaving your views vulnerable to CSRF.

      To create the send_push view, first add the following imports to enable JSON responses and access the send_user_notification function in the webpush library:

      ~/djangopush/djangopush/views.py

      from django.http.response import JsonResponse, HttpResponse
      from django.views.decorators.http import require_GET, require_POST
      from django.shortcuts import get_object_or_404
      from django.contrib.auth.models import User
      from django.views.decorators.csrf import csrf_exempt
      from webpush import send_user_notification
      import json
      

      Next, add the require_POST decorator, which will use the request body sent by the user to create and trigger a push notification:

      ~/djangopush/djangopush/views.py

      @require_GET
      def home(request):
          ...
      
      
      @require_POST
      @csrf_exempt
      def send_push(request):
          try:
              body = request.body
              data = json.loads(body)
      
              if 'head' not in data or 'body' not in data or 'id' not in data:
                  return JsonResponse(status=400, data={"message": "Invalid data format"})
      
              user_id = data['id']
              user = get_object_or_404(User, pk=user_id)
              payload = {'head': data['head'], 'body': data['body']}
              send_user_notification(user=user, payload=payload, ttl=1000)
      
              return JsonResponse(status=200, data={"message": "Web push successful"})
          except TypeError:
              return JsonResponse(status=500, data={"message": "An error occurred"})
      

      We are using two decorators for the send_push view: the require_POST decorator, which restricts the view to POST requests only, and the csrf_exempt decorator, which exempts the view from CSRF protection.

      This view expects POST data and does the following: it gets the body of the request and, using the json package, deserializes the JSON document to a Python object using json.loads. json.loads takes a structured JSON document and converts it to a Python object.

      The view expects the request body object to have three properties:

      • head: The title of the push notification.
      • body: The body of the notification.
      • id: The id of the request user.

      If any of the required properties are missing, the view will return a JSONResponse with a 404 "Not Found" status. If the user with the given primary key exists, the view will return the user with the matching primary key using the get_object_or_404 function from the django.shortcuts library. If the user doesn't exist, the function will return a 404 error.

      The view also makes use of the send_user_notification function from the webpush library. This function takes three parameters:

      • User: The recipient of the push notification.
      • payload: The notification information, which includes the notification head and body.
      • ttl: The maximum time in seconds that the notification should be stored if the user is offline.

      If no errors occur, the view returns a JSONResponse with a 200 "Success" status and a data object. If a KeyError occurs, the view will return a 500 "Internal Server Error" status. A KeyError occurs when the requested key of an object doesn't exist.

      In the next step, we'll create corresponding URL routes to match the views we've created.

      Step 3 — Mapping URLs to Views

      Django makes it possible to create URLs that connect to views with a Python module called a URLconf. This module maps URL path expressions to Python functions (your views). Usually, a URL configuration file is auto-generated when you create a project. In this step, you will update this file to include new routes for the views you created in the previous step, along with the URLs for the django-webpush app, which will provide endpoints to subscribe users to push notifications.

      For more information about views, please see How To Create Django Views.

      Open urls.py:

      • nano ~/djangopush/djangopush/urls.py

      The file will look like this:

      ~/djangopush/djangopush/urls.py

      
      """untitled URL Configuration
      
      The `urlpatterns` list routes URLs to views. For more information please see:
          https://docs.djangoproject.com/en/2.1/topics/http/urls/
      Examples:
      Function views
          1. Add an import:  from my_app import views
          2. Add a URL to urlpatterns:  path('', views.home, name='home')
      Class-based views
          1. Add an import:  from other_app.views import Home
          2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
      Including another URLconf
          1. Import the include() function: from django.urls import include, path
          2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
      """
      from django.contrib import admin
      from django.urls import path
      
      urlpatterns = [
          path('admin/', admin.site.urls),
      ]
      

      The next step is to map the views you've created to URLs. First, add the include import to ensure that all of the routes for the Django-Webpush library will be added to your project:

      ~/djangopush/djangopush/urls.py

      
      """webpushdjango URL Configuration
      ...
      """
      from django.contrib import admin
      from django.urls import path, include
      

      Next, import the views you created in the last step and update the urlpatterns list to map your views:

      ~/djangopush/djangopush/urls.py

      
      """webpushdjango URL Configuration
      ...
      """
      from django.contrib import admin
      from django.urls import path, include
      
      from .views import home, send_push
      
      urlpatterns = [
                        path('admin/', admin.site.urls),
                        path('', home),
                        path('send_push', send_push),
                        path('webpush/', include('webpush.urls')),
                    ]
      

      Here, the urlpatterns list registers the URLs for the django-webpush package and maps your views to the URLs /send_push and /home.

      Let's test the /home view to be sure that it's working as intended. Make sure you're in the root directory of the project:

      Start your server by running the following command:

      • python manage.py runserver your_server_ip:8000

      Navigate to http://your_server_ip:8000. You should see the following home page:

      Initial Home Page view

      At this point, you can kill the server with CTRL+C, and we will move on to creating templates and rendering them in our views using the render function.

      Step 4 — Creating Templates

      Django’s template engine allows you to define the user-facing layers of your application with templates, which are similar to HTML files. In this step, you will create and render a template for the home view.

      Create a folder called templates in your project's root directory:

      • mkdir ~/djangopush/templates

      If you run ls in the root folder of your project at this point, the output will look like this:

      Output

      /djangopush /templates db.sqlite3 manage.py /my_env

      Create a file called home.html in the templates folder:

      • nano ~/djangopush/templates/home.html

      Add the following code to the file to create a form where users can enter information to create push notifications:

      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <meta name="vapid-key" content="{{ vapid_key }}">
          {% if user.id %}
              <meta name="user_id" content="{{ user.id }}">
          {% endif %}
          <title>Web Push</title>
          <link href="https://fonts.googleapis.com/css?family=PT+Sans:400,700" rel="stylesheet">
      </head>
      
      <body>
      <div>
          <form id="send-push__form">
              <h3 class="header">Send a push notification</h3>
              <p class="error"></p>
              <input type="text" name="head" placeholder="Header: Your favorite airline 😍">
              <textarea name="body" id="" cols="30" rows="10" placeholder="Body: Your flight has been cancelled 😱😱😱"></textarea>
              <button>Send Me</button>
          </form>
      </div>
      </body>
      </html>
      

      The body of the file includes a form with two fields: an input element will hold the head/title of the notification and a textarea element will hold the notification body.

      In the head section of the file, there are two meta tags that will hold the VAPID public key and the user's id. These two variables are required to register a user and send them push notifications. The user's id is required here because you'll be sending AJAX requests to the server and the id will be used to identify the user. If the current user is a registered user, then the template will create a meta tag with their id as the content.

      The next step is to tell Django where to find your templates. To do this, you will edit settings.py and update the TEMPLATES list.

      Open the settings.py file:

      • nano ~/djangopush/djangopush/settings.py

      Add the following to the DIRS list to specify the path to the templates directory:

      ~/djangopush/djangopush/settings.py

      ...
      TEMPLATES = [
          {
              'BACKEND': 'django.template.backends.django.DjangoTemplates',
              'DIRS': [os.path.join(BASE_DIR, 'templates')],
              'APP_DIRS': True,
              'OPTIONS': {
                  'context_processors': [
                      ...
                  ],
              },
          },
      ]
      ...
      

      Next, in your views.py file, update the home view to render the home.html template. Open the file:

      • nano ~/djangpush/djangopush/views.py

      First, add some additional imports, including the settings configuration, which contains all of the project's settings from the settings.py file, and the render function from django.shortcuts:

      ~/djangopush/djangopush/views.py

      ...
      from django.shortcuts import render, get_object_or_404
      ...
      import json
      from django.conf import settings
      
      ...
      

      Next, remove the initial code you added to the home view and add the following, which specifies how the template you just created will be rendered:

      ~/djangopush/djangopush/views.py

      ...
      
      @require_GET
      def home(request):
         webpush_settings = getattr(settings, 'WEBPUSH_SETTINGS', {})
         vapid_key = webpush_settings.get('VAPID_PUBLIC_KEY')
         user = request.user
         return render(request, 'home.html', {user: user, 'vapid_key': vapid_key})
      

      The code assigns the following variables:

      • webpush_settings: This is assigned the value of the WEBPUSH_SETTINGS attribute from the settings configuration.
      • vapid_key: This gets the VAPID_PUBLIC_KEY value from the webpush_settings object to send to the client. This public key is checked against the private key to ensure that the client with the public key is permitted to receive push messages from the server.
      • user: This variable comes from the incoming request. Whenever a user makes a request to the server, the details for that user are stored in the user field.

      The render function will return an HTML file and a context object containing the current user and the server's vapid public key. It takes three parameters here: the request, the template to be rendered, and the object that contains the variables that will be used in the template.

      With our template created and the home view updated, we can move on to configuring Django to serve our static files.

      Step 5 — Serving Static Files

      Web applications include CSS, JavaScript, and other image files that Django refers to as “static files”. Django allows you to collect all of the static files from each application in your project into a single location from which they are served. This solution is called django.contrib.staticfiles. In this step, we'll update our settings to tell Django where our static files will be stored.

      Open settings.py:

      • nano ~/djangopush/djangopush/settings.py

      In settings.py, first ensure that the STATIC_URL has been defined:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      

      Next, add a list of directories called STATICFILES_DIRS where Django will look for static files:

      ~/djangopush/djangopush/settings.py

      ...
      STATIC_URL = '/static/'
      STATICFILES_DIRS = [
          os.path.join(BASE_DIR, "static"),
      ]
      

      You can now add the STATIC_URL to the list of paths defined in your urls.py file.

      Open the file:

      • nano ~/djangopush/djangopush/urls.py

      Add the following code, which will import the static url configuration and update the urlpatterns list. The helper function here uses the STATIC_URL and STATIC_ROOT properties we provided in the settings.py file to serve the project's static files:

      ~/djangopush/djangopush/urls.py

      
      ...
      from django.conf import settings
      from django.conf.urls.static import static
      
      urlpatterns = [
          ...
      ]  + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
      

      With our static files settings configured, we can move on to styling the application's home page.

      Step 6 — Styling the Home Page

      After setting up your application to serve static files, you can create an external stylesheet and link it to the home.html file to style the home page. All of your static files will be stored in a static directory in the root folder of your project.

      Create a static folder and a css folder within the static folder:

      • mkdir -p ~/djangopush/static/css

      Open a css file called styles.css inside the css folder:

      • nano ~/djangopush/static/css/styles.css

      Add the following styles for the home page:

      ~/djangopush/static/css/styles.css

      
      body {
          height: 100%;
          background: rgba(0, 0, 0, 0.87);
          font-family: 'PT Sans', sans-serif;
      }
      
      div {
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
      }
      
      form {
          display: flex;
          flex-direction: column;
          align-items: center;
          justify-content: center;
          width: 35%;
          margin: 10% auto;
      }
      
      form > h3 {
          font-size: 17px;
          font-weight: bold;
          margin: 15px 0;
          color: orangered;
          text-transform: uppercase;
      }
      
      form > .error {
          margin: 0;
          font-size: 15px;
          font-weight: normal;
          color: orange;
          opacity: 0.7;
      }
      
      form > input, form > textarea {
          border: 3px solid orangered;
          box-shadow: unset;
          padding: 13px 12px;
          margin: 12px auto;
          width: 80%;
          font-size: 13px;
          font-weight: 500;
      }
      
      form > input:focus, form > textarea:focus {
          border: 3px solid orangered;
          box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
          outline: unset;
      }
      
      form > button {
          justify-self: center;
          padding: 12px 25px;
          border-radius: 0;
          text-transform: uppercase;
          font-weight: 600;
          background: orangered;
          color: white;
          border: none;
          font-size: 14px;
          letter-spacing: -0.1px;
          cursor: pointer;
      }
      
      form > button:disabled {
          background: dimgrey;
          cursor: not-allowed;
      }
      

      With the stylesheet created, you can link it to the home.html file using static template tags. Open the home.html file:

      • nano ~/djangopush/templates/home.html

      Update the head section to include a link to the external stylesheet:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
          ...
          <link href="http://www.digitalocean.com/{% static"/css/styles.css' %}" rel="stylesheet">
      </head>
      <body>
          ...
      </body>
      </html>
      

      Make sure that you are in your main project directory and start your server again to inspect your work:

      • cd ~/djangopush
      • python manage.py runserver your_server_ip:8000

      When you visit http://your_server_ip:8000, it should look like this:

      Home page view
      Again, you can kill the server with CTRL+C.

      Now that you have successfully created the home.html page and styled it, you can subscribe users to push notifications whenever they visit the home page.

      Step 7 — Registering a Service Worker and Subscribing Users to Push Notifications

      Web push notifications can notify users when there are updates to applications they are subscribed to or prompt them to re-engage with applications they have used in the past. They rely on two technologies, the push API and the notifications API. Both technologies rely on the presence of a service worker.

      A push is invoked when the server provides information to the service worker and the service worker uses the notifications API to display this information.

      We'll subscribe our users to the push and then we'll send the information from the subscription to the server to register them.

      In the static directory, create a folder called js:

      • mkdir ~/djangopush/static/js

      Create a file called registerSw.js:

      • nano ~/djangopush/static/js/registerSw.js

      Add the following code, which checks if service workers are supported on the user's browser before attempting to register a service worker:

      ~/djangopush/static/js/registerSw.js

      
      const registerSw = async () => {
          if ('serviceWorker' in navigator) {
              const reg = await navigator.serviceWorker.register('sw.js');
              initialiseState(reg)
      
          } else {
              showNotAllowed("You can't send push notifications ☹️😢")
          }
      };
      

      First, the registerSw function checks if the browser supports service workers before registering them. After registration, it calls the initializeState function with the registration data. If service workers are not supported in the browser, it calls the showNotAllowed function.

      Next, add the following code below the registerSw function to check if a user is eligible to receive push notifications before attempting to subscribe them:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      const initialiseState = (reg) => {
          if (!reg.showNotification) {
              showNotAllowed('Showing notifications isn't supported ☹️😢');
              return
          }
          if (Notification.permission === 'denied') {
              showNotAllowed('You prevented us from showing notifications ☹️🤔');
              return
          }
          if (!'PushManager' in window) {
              showNotAllowed("Push isn't allowed in your browser 🤔");
              return
          }
          subscribe(reg);
      }
      
      const showNotAllowed = (message) => {
          const button = document.querySelector('form>button');
          button.innerHTML = `${message}`;
          button.setAttribute('disabled', 'true');
      };
      

      The initializeState function checks the following:

      • Whether or not the user has enabled notifications, using the value of reg.showNotification.
      • Whether or not the user has granted the application permission to display notifications.
      • Whether or not the browser supports the PushManager API.
        If any of these checks fail, the showNotAllowed function is called and the subscription is aborted.

      The showNotAllowed function displays a message on the button and disables it if a user is ineligible to receive notifications. It also displays appropriate messages if a user has restricted the application from displaying notifications or if the browser doesn't support push notifications.

      Once we ensure that a user is eligible to receive push notifications, the next step is to subscribe them using pushManager. Add the following code below the showNotAllowed function:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      function urlB64ToUint8Array(base64String) {
          const padding = '='.repeat((4 - base64String.length % 4) % 4);
          const base64 = (base64String + padding)
              .replace(/-/g, '+')
              .replace(/_/g, '/');
      
          const rawData = window.atob(base64);
          const outputArray = new Uint8Array(rawData.length);
          const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
      
          return outputData;
      }
      
      const subscribe = async (reg) => {
          const subscription = await reg.pushManager.getSubscription();
          if (subscription) {
              sendSubData(subscription);
              return;
          }
      
          const vapidMeta = document.querySelector('meta[name="vapid-key"]');
          const key = vapidMeta.content;
          const options = {
              userVisibleOnly: true,
              // if key exists, create applicationServerKey property
              ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
          };
      
          const sub = await reg.pushManager.subscribe(options);
          sendSubData(sub)
      };
      

      Calling the pushManager.getSubscription function returns the data for an active subscription. When an active subscription exists, the sendSubData function is called with the subscription info passed in as a parameter.

      When no active subscription exists, the VAPID public key, which is Base64 URL-safe encoded, is converted to a Uint8Array using the urlB64ToUint8Array function. pushManager.subscribe is then called with the VAPID public key and the userVisible value as options. You can read more about the available options here.

      After successfully subscribing a user, the next step is to send the subscription data to the server. The data will be sent to the webpush/save_information endpoint provided by the django-webpush package. Add the following code below the subscribe function:

      ~/djangopush/static/js/registerSw.js

      
      ...
      
      const sendSubData = async (subscription) => {
          const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
          const data = {
              status_type: 'subscribe',
              subscription: subscription.toJSON(),
              browser: browser,
          };
      
          const res = await fetch('/webpush/save_information', {
              method: 'POST',
              body: JSON.stringify(data),
              headers: {
                  'content-type': 'application/json'
              },
              credentials: "include"
          });
      
          handleResponse(res);
      };
      
      const handleResponse = (res) => {
          console.log(res.status);
      };
      
      registerSw();
      

      The save_information endpoint requires information about the status of the subscription (subscribe and unsubscribe), the subscription data, and the browser. Finally, we call the registerSw() function to begin the process of subscribing the user.

      The completed file looks like this:

      ~/djangopush/static/js/registerSw.js

      
      const registerSw = async () => {
          if ('serviceWorker' in navigator) {
              const reg = await navigator.serviceWorker.register('sw.js');
              initialiseState(reg)
      
          } else {
              showNotAllowed("You can't send push notifications ☹️😢")
          }
      };
      
      const initialiseState = (reg) => {
          if (!reg.showNotification) {
              showNotAllowed('Showing notifications isn't supported ☹️😢');
              return
          }
          if (Notification.permission === 'denied') {
              showNotAllowed('You prevented us from showing notifications ☹️🤔');
              return
          }
          if (!'PushManager' in window) {
              showNotAllowed("Push isn't allowed in your browser 🤔");
              return
          }
          subscribe(reg);
      }
      
      const showNotAllowed = (message) => {
          const button = document.querySelector('form>button');
          button.innerHTML = `${message}`;
          button.setAttribute('disabled', 'true');
      };
      
      function urlB64ToUint8Array(base64String) {
          const padding = '='.repeat((4 - base64String.length % 4) % 4);
          const base64 = (base64String + padding)
              .replace(/-/g, '+')
              .replace(/_/g, '/');
      
          const rawData = window.atob(base64);
          const outputArray = new Uint8Array(rawData.length);
          const outputData = outputArray.map((output, index) => rawData.charCodeAt(index));
      
          return outputData;
      }
      
      const subscribe = async (reg) => {
          const subscription = await reg.pushManager.getSubscription();
          if (subscription) {
              sendSubData(subscription);
              return;
          }
      
          const vapidMeta = document.querySelector('meta[name="vapid-key"]');
          const key = vapidMeta.content;
          const options = {
              userVisibleOnly: true,
              // if key exists, create applicationServerKey property
              ...(key && {applicationServerKey: urlB64ToUint8Array(key)})
          };
      
          const sub = await reg.pushManager.subscribe(options);
          sendSubData(sub)
      };
      
      const sendSubData = async (subscription) => {
          const browser = navigator.userAgent.match(/(firefox|msie|chrome|safari|trident)/ig)[0].toLowerCase();
          const data = {
              status_type: 'subscribe',
              subscription: subscription.toJSON(),
              browser: browser,
          };
      
          const res = await fetch('/webpush/save_information', {
              method: 'POST',
              body: JSON.stringify(data),
              headers: {
                  'content-type': 'application/json'
              },
              credentials: "include"
          });
      
          handleResponse(res);
      };
      
      const handleResponse = (res) => {
          console.log(res.status);
      };
      
      registerSw();
      

      Next, add a script tag for the registerSw.js file in home.html. Open the file:

      • nano ~/djangopush/templates/home.html

      Add the script tag before the closing tag of the body element:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
         ...
      </head>
      <body>
         ...
         <script src="https://www.digitalocean.com/{% static"/js/registerSw.js' %}"></script>
      </body>
      </html>
      

      Because a service worker doesn't yet exist, if you left your application running or tried to start it again, you would see an error message. Let's fix this by creating a service worker.

      Step 8 — Creating a Service Worker

      To display a push notification, you'll need an active service worker installed on your application's home page. We'll create a service worker that listens for push events and displays the messages when ready.

      Because we want the scope of the service worker to be the entire domain, we will need to install it in the application's root. You can read more about the process in this article outlining how to register a service worker. Our approach will be to create a sw.js file in the templates folder, which we will then register as a view.

      Create the file:

      • nano ~/djangopush/templates/sw.js

      Add the following code, which tells the service worker to listen for push events:

      ~/djangopush/templates/sw.js

      
      // Register event listener for the 'push' event.
      self.addEventListener('push', function (event) {
          // Retrieve the textual payload from event.data (a PushMessageData object).
          // Other formats are supported (ArrayBuffer, Blob, JSON), check out the documentation
          // on https://developer.mozilla.org/en-US/docs/Web/API/PushMessageData.
          const eventInfo = event.data.text();
          const data = JSON.parse(eventInfo);
          const head = data.head || 'New Notification 🕺🕺';
          const body = data.body || 'This is default content. Your notification didn't have one 🙄🙄';
      
          // Keep the service worker alive until the notification is created.
          event.waitUntil(
              self.registration.showNotification(head, {
                  body: body,
                  icon: 'https://i.imgur.com/MZM3K5w.png'
              })
          );
      });
      

      The service worker listens for a push event. In the callback function, the event data is converted to text. We use default title and body strings if the event data doesn't have them. The showNotification function takes the notification title, the header of the notification to be displayed, and an options object as parameters. The options object contains several properties to configure the visual options of a notification.

      For your service worker to work for the entirety of your domain, you will need to install it in the root of the application. We'll use TemplateView to allow the service worker access to the whole domain.

      Open the urls.py file:

      • nano ~/djangopush/djangopush/urls.py

      Add a new import statement and path in the urlpatterns list to create a class-based view:

      ~/djangopush/djangopush/urls.py

      ...
      from django.views.generic import TemplateView
      
      urlpatterns = [
                        ...,
                        path('sw.js', TemplateView.as_view(template_name='sw.js', content_type='application/x-javascript'))
                    ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
      

      Class-based views like TemplateView allow you to create flexible, reusable views. In this case, the TemplateView.as_view method creates a path for the service worker by passing the recently created service worker as a template and application/x-javascript as the content_type of the template.

      You have now created a service worker and registered it as a route. Next, you'll set up the form on the home page to send push notifications.

      Step 9 — Sending Push Notifications

      Using the form on the home page, users should be able to send push notifications while your server is running. You can also send push notifications using any RESTful service like Postman. When the user sends push notifications from the form on the home page, the data will include a head and body, as well as the id of the receiving user. The data should be structured in the following manner:

      {
          head: "Title of the notification",
          body: "Notification body",
          id: "User's id"
      }
      

      To listen for the submit event of the form and send the data entered by the user to the server, we will create a file called site.js in the ~/djangopush/static/js directory.

      Open the file:

      • nano ~/djangopush/static/js/site.js

      First, add a submit event listener to the form that will enable you to get the values of the form inputs and the user id stored in the meta tag of your template:

      ~/djangopush/static/js/site.js

      
      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
          e.preventDefault();
          const input = this[0];
          const textarea = this[1];
          const button = this[2];
          errorMsg.innerText = '';
      
          const head = input.value;
          const body = textarea.value;
          const meta = document.querySelector('meta[name="user_id"]');
          const id = meta ? meta.content : null;
          ...
          // TODO: make an AJAX request to send notification
      });
      

      The pushForm function gets the input, textarea, and button inside the form. It also gets the information from the meta tag, including the name attribute user_id and the user's id stored in the content attribute of the tag. With this information, it can send a POST request to the /send_push endpoint on the server.

      To send requests to the server, we'll use the native Fetch API. We're using Fetch here because it is supported by most browsers and doesn't require external libraries to function. Below the code you've added, update the pushForm function to include the code for sending AJAX requests:

      ~/djangopush/static/js/site.js

      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
           ...
          const id = meta ? meta.content : null;
      
           if (head && body && id) {
              button.innerText = 'Sending...';
              button.disabled = true;
      
              const res = await fetch('/send_push', {
                  method: 'POST',
                  body: JSON.stringify({head, body, id}),
                  headers: {
                      'content-type': 'application/json'
                  }
              });
              if (res.status === 200) {
                  button.innerText = 'Send another 😃!';
                  button.disabled = false;
                  input.value = '';
                  textarea.value = '';
              } else {
                  errorMsg.innerText = res.message;
                  button.innerText = 'Something broke 😢..  Try again?';
                  button.disabled = false;
              }
          }
          else {
              let error;
              if (!head || !body){
                  error = 'Please ensure you complete the form 🙏🏾'
              }
              else if (!id){
                  error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
              }
              errorMsg.innerText = error;
          }
      });
      

      If the three required parameters head, body, and id are present, we send the request and disable the submit button temporarily.

      The completed file looks like this:

      ~/djangopush/static/js/site.js

      const pushForm = document.getElementById('send-push__form');
      const errorMsg = document.querySelector('.error');
      
      pushForm.addEventListener('submit', async function (e) {
          e.preventDefault();
          const input = this[0];
          const textarea = this[1];
          const button = this[2];
          errorMsg.innerText = '';
      
          const head = input.value;
          const body = textarea.value;
          const meta = document.querySelector('meta[name="user_id"]');
          const id = meta ? meta.content : null;
      
          if (head && body && id) {
              button.innerText = 'Sending...';
              button.disabled = true;
      
              const res = await fetch('/send_push', {
                  method: 'POST',
                  body: JSON.stringify({head, body, id}),
                  headers: {
                      'content-type': 'application/json'
                  }
              });
              if (res.status === 200) {
                  button.innerText = 'Send another 😃!';
                  button.disabled = false;
                  input.value = '';
                  textarea.value = '';
              } else {
                  errorMsg.innerText = res.message;
                  button.innerText = 'Something broke 😢..  Try again?';
                  button.disabled = false;
              }
          }
          else {
              let error;
              if (!head || !body){
                  error = 'Please ensure you complete the form 🙏🏾'
              }
              else if (!id){
                  error = "Are you sure you're logged in? 🤔. Make sure! 👍🏼"
              }
              errorMsg.innerText = error;
          }    
      });
      

      Finally, add the site.js file to home.html:

      • nano ~/djangopush/templates/home.html

      Add the script tag:

      ~/djangopush/templates/home.html

      
      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
      
      <head>
         ...
      </head>
      <body>
         ...
         <script src="https://www.digitalocean.com/{% static"/js/site.js' %}"></script>
      </body>
      </html>
      

      At this point, if you left your application running or tried to start it again, you would see an error, since service workers can only function in secure domains or on localhost. In the next step we'll use ngrok to create a secure tunnel to our web server.

      Step 10 — Creating a Secure Tunnel to Test the Application

      Service workers require secure connections to function on any site except localhost since they can allow connections to be hijacked and responses to be filtered and fabricated. For this reason, we'll create a secure tunnel for our server with ngrok.

      Open a second terminal window and ensure you're in your home directory:

      If you started with a clean 18.04 server in the prerequisites, then you will need to install unzip:

      • sudo apt update && sudo apt install unzip

      Download ngrok:

      • wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
      • unzip ngrok-stable-linux-amd64.zip

      Move ngrok to /usr/local/bin, so that you will have access to the ngrok command from the terminal:

      • sudo mv ngrok /usr/local/bin

      In your first terminal window, make sure that you are in your project directory and start your server:

      • cd ~/djangopush
      • python manage.py runserver your_server_ip:8000

      You will need to do this before creating a secure tunnel for your application.

      In your second terminal window, navigate to your project folder, and activate your virtual environment:

      • cd ~/djangopush
      • source my_env/bin/activate

      Create the secure tunnel to your application:

      • ngrok http your_server_ip:8000

      You will see the following output, which includes information about your secure ngrok URL:

      Output

      ngrok by @inconshreveable (Ctrl+C to quit) Session Status online Session Expires 7 hours, 59 minutes Version 2.2.8 Region United States (us) Web Interface http://127.0.0.1:4040 Forwarding http://ngrok_secure_url -> 203.0.113.0:8000 Forwarding https://ngrok_secure_url -> 203.0.113.0:8000 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00

      Copy the ngrok_secure_url from the console output. You will need to add it to the list of ALLOWED_HOSTS in your settings.py file.

      Open another terminal window, navigate to your project folder, and activate your virtual environment:

      • cd ~/djangopush
      • source my_env/bin/activate

      Open the settings.py file:

      • nano ~/djangopush/djangopush/settings.py

      Update the list of ALLOWED_HOSTS with the ngrok secure tunnel:

      ~/djangopush/djangopush/settings.py

      ...
      
      ALLOWED_HOSTS = ['your_server_ip', 'ngrok_secure_url']
      ...
      
      

      Navigate to the secure admin page to log in: https://ngrok_secure_url/admin/. You will see a screen that looks like this:

      ngrok admin login

      Enter your Django admin user information on this screen. This should be the same information you entered when you logged into the admin interface in the prerequisite steps. You are now ready to send push notifications.

      Visit https://ngrok_secure_url in your browser. You will see a prompt asking for permission to display notifications. Click the Allow button to let your browser display push notifications:

      push notifications request

      Submitting a filled form will display a notification similar to this:

      screenshot of notification

      Note: Be sure that your server is running before attempting to send notifications.

      If you received notifications then your application is working as expected.

      You have created a web application that triggers push notifications on the server and, with the help of service workers, receives and displays notifications. You also went through the steps of obtaining the VAPID keys that are required to send push notifications from an application server.

      Conclusion

      In this tutorial, you've learned how to subscribe users to push notifications, install service workers, and display push notifications using the notifications API.

      You can go even further by configuring the notifications to open specific areas of your application when clicked. The source code for this tutorial can be found here.



      Source link