23 |
24 | {% endblock %}
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Django's command-line utility for administrative tasks."""
3 | import os
4 | import sys
5 |
6 |
7 | def main():
8 | """Run administrative tasks."""
9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangofcm.settings')
10 | try:
11 | from django.core.management import execute_from_command_line
12 | except ImportError as exc:
13 | raise ImportError(
14 | "Couldn't import Django. Are you sure it's installed and "
15 | "available on your PYTHONPATH environment variable? Did you "
16 | "forget to activate a virtual environment?"
17 | ) from exc
18 | execute_from_command_line(sys.argv)
19 |
20 |
21 | if __name__ == '__main__':
22 | main()
23 |
--------------------------------------------------------------------------------
/templates/notifications/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static %}
3 | {% load crispy_forms_tags %}
4 |
5 | {% block body %}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Test Notification
14 |
19 |
20 |
21 |
22 |
23 |
24 | {% endblock %}
25 |
26 | {% block js %}
27 |
28 |
29 | {% endblock %}
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Seyyed Ali Ayati
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/notifications/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, redirect
2 | from django.contrib import messages
3 | from django.contrib.auth.decorators import login_required
4 |
5 | from .forms import TestNotification
6 |
7 | from firebase_admin.messaging import Message
8 | from fcm_django.models import FCMDevice
9 |
10 |
11 | @login_required
12 | def index_page(request):
13 | if request.method == "POST":
14 | form = TestNotification(request.POST)
15 | if form.is_valid():
16 | message_obj = Message(
17 | data={
18 | 'title': form.cleaned_data.get('title'),
19 | 'body': form.cleaned_data.get('body'),
20 | 'icon_url': form.cleaned_data.get('icon_url'),
21 | 'url': form.cleaned_data.get('url'),
22 | },
23 | )
24 | devices = FCMDevice.objects.filter(user_id=form.cleaned_data.get('user_id'))
25 | if devices.exists():
26 | print(devices)
27 | devices.send_message(message_obj)
28 | return redirect('index')
29 | messages.error(
30 | request, "Unsuccessful. Invalid information.")
31 | form = TestNotification()
32 | return render(request, 'notifications/index.html', context={'form': form})
33 |
--------------------------------------------------------------------------------
/djangofcm/urls.py:
--------------------------------------------------------------------------------
1 | """djangofcm URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: path('', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.urls import include, path
14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
15 | """
16 | from django.contrib import admin
17 | from django.urls import path, include
18 | from django.conf import settings
19 | from django.conf.urls.static import static
20 | from django.views.generic import TemplateView
21 |
22 | from fcm_django.api.rest_framework import FCMDeviceAuthorizedViewSet
23 | from rest_framework.routers import DefaultRouter
24 |
25 | router = DefaultRouter()
26 | router.register('devices', FCMDeviceAuthorizedViewSet)
27 |
28 | urlpatterns = [
29 | path("firebase-messaging-sw.js",
30 | TemplateView.as_view(
31 | template_name="firebase-messaging-sw.js",
32 | content_type="application/javascript",
33 | ),
34 | name="firebase-messaging-sw.js"
35 | ),
36 | path('admin/', admin.site.urls),
37 | path('api/', include(router.urls)),
38 | path('', include('notifications.urls')),
39 | path('accounts/', include('accounts.urls')),
40 | ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
41 |
--------------------------------------------------------------------------------
/templates/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | // [START initialize_firebase_in_sw]
2 | // Give the service worker access to Firebase Messaging.
3 | // Note that you can only use Firebase Messaging here, other Firebase libraries
4 | // are not available in the service worker.
5 | importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
6 | importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
7 |
8 | // Initialize the Firebase app in the service worker by passing in the
9 | // messagingSenderId.
10 | firebase.initializeApp({
11 | // Replace messagingSenderId with yours
12 | 'messagingSenderId': '729476541475'
13 | });
14 |
15 | // Retrieve an instance of Firebase Messaging so that it can handle background
16 | // messages.
17 | const messaging = firebase.messaging();
18 | // [END initialize_firebase_in_sw]
19 |
20 | // If you would like to customize notifications that are received in the
21 | // background (Web app is closed or not in browser focus) then you should
22 | // implement this optional method.
23 | // [START background_handler]
24 | messaging.setBackgroundMessageHandler(function(payload) {
25 | console.log('[firebase-messaging-sw.js] Received background message ', payload);
26 | // Customize notification here
27 | payload = payload.data;
28 | const notificationTitle = payload.title;
29 | const notificationOptions = {
30 | body: payload.body,
31 | icon: payload.icon_url,
32 | };
33 |
34 | self.addEventListener('notificationclick', function (event) {
35 | event.notification.close();
36 | clients.openWindow(payload.url);
37 | });
38 |
39 | return self.registration.showNotification(notificationTitle,
40 | notificationOptions);
41 | });
42 | // [END background_handler]
--------------------------------------------------------------------------------
/accounts/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render, redirect
2 | from django.contrib import messages
3 | from django.contrib.auth import login, authenticate, logout
4 | from django.contrib.auth.forms import AuthenticationForm
5 |
6 | from .forms import NewUserForm
7 |
8 |
9 | def register_page(request):
10 | if request.method == "POST":
11 | form = NewUserForm(request.POST)
12 | if form.is_valid():
13 | user = form.save()
14 | login(request, user)
15 | messages.success(request, "Registration successful.")
16 | return redirect("login")
17 | messages.error(
18 | request, "Unsuccessful registration. Invalid information.")
19 | form = NewUserForm()
20 | return render(request=request, template_name="accounts/register.html", context={"register_form": form})
21 |
22 |
23 | def login_page(request):
24 | if request.method == "POST":
25 | form = AuthenticationForm(request, data=request.POST)
26 | if form.is_valid():
27 | username = form.cleaned_data.get('username')
28 | password = form.cleaned_data.get('password')
29 | user = authenticate(username=username, password=password)
30 | if user is not None:
31 | login(request, user)
32 | messages.info(request, f"You are now logged in as {username}.")
33 | return redirect("index")
34 | else:
35 | messages.error(request, "Invalid username or password.")
36 | else:
37 | messages.error(request, "Invalid username or password.")
38 | form = AuthenticationForm()
39 | return render(request=request, template_name="accounts/login.html", context={"login_form": form})
40 |
41 | def logout_request(request):
42 | logout(request)
43 | messages.info(request, "You have successfully logged out.")
44 | return redirect("index")
45 |
--------------------------------------------------------------------------------
/djangofcm/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for djangofcm project.
3 |
4 | Generated by 'django-admin startproject' using Django 3.2.9.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/3.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/3.2/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 | from firebase_admin import initialize_app
15 |
16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
17 | BASE_DIR = Path(__file__).resolve().parent.parent
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = 'django-insecure-4g-ep^qaqrr+^*=qztt68e&qy-)kcs6y@)l1xzdmzrvtcn1w7c'
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = []
30 |
31 |
32 | # Application definition
33 |
34 | INSTALLED_APPS = [
35 | 'django.contrib.admin',
36 | 'django.contrib.auth',
37 | 'django.contrib.contenttypes',
38 | 'django.contrib.sessions',
39 | 'django.contrib.messages',
40 | 'django.contrib.staticfiles',
41 | 'accounts.apps.AccountsConfig',
42 | 'notifications.apps.NotificationsConfig',
43 | 'crispy_forms',
44 | 'rest_framework',
45 | 'fcm_django',
46 | ]
47 |
48 | MIDDLEWARE = [
49 | 'django.middleware.security.SecurityMiddleware',
50 | 'django.contrib.sessions.middleware.SessionMiddleware',
51 | 'django.middleware.common.CommonMiddleware',
52 | 'django.middleware.csrf.CsrfViewMiddleware',
53 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
54 | 'django.contrib.messages.middleware.MessageMiddleware',
55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
56 | ]
57 |
58 | ROOT_URLCONF = 'djangofcm.urls'
59 |
60 | TEMPLATES = [
61 | {
62 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
63 | 'DIRS': [BASE_DIR / 'templates', ],
64 | 'APP_DIRS': True,
65 | 'OPTIONS': {
66 | 'context_processors': [
67 | 'django.template.context_processors.debug',
68 | 'django.template.context_processors.request',
69 | 'django.contrib.auth.context_processors.auth',
70 | 'django.contrib.messages.context_processors.messages',
71 | ],
72 | },
73 | },
74 | ]
75 |
76 | CRISPY_TEMPLATE_PACK = 'bootstrap4'
77 | MESSAGE_TAGS = {
78 | 10: 'alert-secondary',
79 | 20: 'alert-info',
80 | 25: 'alert-success',
81 | 30: 'alert-warning',
82 | 40: 'alert-danger',
83 | }
84 |
85 | WSGI_APPLICATION = 'djangofcm.wsgi.application'
86 |
87 |
88 | # Database
89 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases
90 |
91 | DATABASES = {
92 | 'default': {
93 | 'ENGINE': 'django.db.backends.sqlite3',
94 | 'NAME': BASE_DIR / 'db.sqlite3',
95 | }
96 | }
97 |
98 |
99 | # Password validation
100 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
101 |
102 | AUTH_PASSWORD_VALIDATORS = [
103 | {
104 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
105 | },
106 | {
107 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
108 | },
109 | {
110 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
111 | },
112 | {
113 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
114 | },
115 | ]
116 |
117 |
118 | # Internationalization
119 | # https://docs.djangoproject.com/en/3.2/topics/i18n/
120 |
121 | LANGUAGE_CODE = 'en-us'
122 |
123 | TIME_ZONE = 'UTC'
124 |
125 | USE_I18N = True
126 |
127 | USE_L10N = True
128 |
129 | USE_TZ = True
130 |
131 |
132 | # Static files (CSS, JavaScript, Images)
133 | # https://docs.djangoproject.com/en/3.2/howto/static-files/
134 |
135 | STATIC_URL = '/static/'
136 | STATICFILES_DIRS = [
137 | BASE_DIR / 'static',
138 | ]
139 |
140 | # Default primary key field type
141 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
142 |
143 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
144 |
145 | # Optional ONLY IF you have initialized a firebase app already:
146 | # Visit https://firebase.google.com/docs/admin/setup/#python
147 | # for more options for the following:
148 | # Store an environment variable called GOOGLE_APPLICATION_CREDENTIALS
149 | # which is a path that point to a json file with your credentials.
150 | # Additional arguments are available: credentials, options, name
151 | FIREBASE_APP = initialize_app()
152 | # To learn more, visit the docs here:
153 | # https://cloud.google.com/docs/authentication/getting-started>
154 |
155 | FCM_DJANGO_SETTINGS = {
156 | # default: _('FCM Django')
157 | # "APP_VERBOSE_NAME": "[string for AppConfig's verbose_name]",
158 | # true if you want to have only one active device per registered user at a time
159 | # default: False
160 | "ONE_DEVICE_PER_USER": False,
161 | # devices to which notifications cannot be sent,
162 | # are deleted upon receiving error response from FCM
163 | # default: False
164 | "DELETE_INACTIVE_DEVICES": False,
165 | }
166 |
--------------------------------------------------------------------------------
/templates/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% block title %}{% endblock %}
9 |
10 |
11 |
12 |
13 |
14 |
39 |
40 | {% for message in messages %}
41 |
42 |
43 |
46 | {{ message }}
47 |
48 |
49 | {% endfor %}
50 |
51 |
52 | {% block body %}{% endblock %}
53 |
54 |
55 |
56 |
104 |
105 |
106 |
107 |
108 |
109 | {% block js %}{% endblock %}
110 |
111 |
112 |
--------------------------------------------------------------------------------
/static/js/index.js:
--------------------------------------------------------------------------------
1 | const firebaseConfig = {
2 | // Replace this with your config; otherwise it will not work!
3 | apiKey: "AIzaSyDGU5p3PoV1e7CyATtK6llT6gSYCzcvcs8",
4 | authDomain: "djangofcm-aa5e1.firebaseapp.com",
5 | projectId: "djangofcm-aa5e1",
6 | storageBucket: "djangofcm-aa5e1.appspot.com",
7 | messagingSenderId: "729476541475",
8 | appId: "1:729476541475:web:c8c134008274621fa45596"
9 | };
10 |
11 | firebase.initializeApp(firebaseConfig);
12 | console.log("Firebase initialized...");
13 | // Firebase Messaging Service
14 | const messaging = firebase.messaging();
15 |
16 | function sendTokenToServer(currentToken) {
17 | if (!isTokenSentToServer()) {
18 | // The API Endpoint will be explained at step 8
19 | $.ajax({
20 | url: "/api/devices/",
21 | method: "POST",
22 | async: false,
23 | data: {
24 | 'registration_id': currentToken,
25 | 'type': 'web'
26 | },
27 | success: function (data) {
28 | console.log(data);
29 | setTokenSentToServer(true);
30 | },
31 | error: function (err) {
32 | console.log(err);
33 | setTokenSentToServer(false);
34 | }
35 | });
36 |
37 | } else {
38 | console.log('Token already sent to server so won\'t send it again ' +
39 | 'unless it changes');
40 | }
41 | }
42 |
43 | function isTokenSentToServer() {
44 | return window.localStorage.getItem("sentToServer") === "1";
45 | }
46 |
47 | function setTokenSentToServer(sent) {
48 | if (sent) {
49 | window.localStorage.setItem("sentToServer", "1");
50 | } else {
51 | window.localStorage.setItem("sentToServer", "0");
52 | }
53 | }
54 |
55 |
56 | function requestPermission() {
57 | messaging.requestPermission().then(function () {
58 | console.log("Has permission!");
59 | resetUI();
60 | }).catch(function (err) {
61 | console.log('Unable to get permission to notify.', err);
62 | });
63 | }
64 |
65 | function resetUI() {
66 | console.log("In reset ui");
67 | messaging.getToken().then(function (currentToken) {
68 | console.log(currentToken);
69 | if (currentToken) {
70 | sendTokenToServer(currentToken);
71 | } else {
72 | setTokenSentToServer(false);
73 | }
74 | }).catch(function (err) {
75 | console.log(err);
76 | setTokenSentToServer(false);
77 | });
78 | }
79 |
80 |
81 | $('document').ready(function () {
82 | // Document is ready.
83 | console.log("loaded index.js");
84 | // Setup AJAX
85 | $.ajaxSetup({
86 | beforeSend: function (xhr, settings) {
87 | function getCookie(name) {
88 | var cookieValue = null;
89 | if (document.cookie && document.cookie != '') {
90 | var cookies = document.cookie.split(';');
91 | for (var i = 0; i < cookies.length; i++) {
92 | var cookie = jQuery.trim(cookies[i]);
93 | // Does this cookie string begin with the name we want?
94 | if (cookie.substring(0, name.length + 1) == (name + '=')) {
95 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
96 | break;
97 | }
98 | }
99 | }
100 | return cookieValue;
101 | }
102 |
103 | if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
104 | // Only send the token to relative URLs i.e. locally.
105 | xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
106 | }
107 | }
108 | });
109 |
110 | messaging.onTokenRefresh(function () {
111 | messaging.getToken().then(function (refreshedToken) {
112 | console.log("Token refreshed.");
113 | // Indicate that the new Instance ID token has not yet been sent to the
114 | // app server.
115 | setTokenSentToServer(false);
116 | // Send Instance ID token to app server.
117 | sendTokenToServer(refreshedToken);
118 | resetUI();
119 | }).catch(function (err) {
120 | console.log("Unable to retrieve refreshed token ", err);
121 | });
122 | });
123 |
124 | messaging.onMessage(function (payload) {
125 | payload = payload.data;
126 | // Create notification manually when user is focused on the tab
127 | const notificationTitle = payload.title;
128 | const notificationOptions = {
129 | body: payload.body,
130 | icon: payload.icon_url,
131 | };
132 |
133 | if (!("Notification" in window)) {
134 | console.log("This browser does not support system notifications");
135 | }
136 | // Let's check whether notification permissions have already been granted
137 | else if (Notification.permission === "granted") {
138 | // If it's okay let's create a notification
139 | var notification = new Notification(notificationTitle, notificationOptions);
140 | notification.onclick = function (event) {
141 | event.preventDefault(); // prevent the browser from focusing the Notification's tab
142 | window.open(payload.url, '_blank');
143 | notification.close();
144 | }
145 | }
146 | });
147 |
148 | requestPermission();
149 | });
150 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | seyyedaliayati@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Adding Firebase Cloud Messaging Service into a Django Project
2 | The aim of this repository is to provide a step-by-step guide and a basic project sample to implement FCM (Firebase Cloud Messaging) feature into a django-based project.
3 |
4 | # Step 1: Create a Firebase Project
5 | Goto [firebase console](https://console.firebase.google.com/) and click on `Add project` to create a new firebase project. Since the goal of using firebase is its cloud messaging feature, you can disable Google Analytics.
6 |
7 | # Step 2: Download a JSON File Including Project's Credentials
8 | Next to the `Project Overview` on the left menu there is a gear icon which redirects you to the project settings page.
9 |
10 | 
11 |
12 | Then, click on `Service accounts` and then click on `Generate new private key`. Download and store the JSON file on your device.
13 |
14 | **Security Note: Do NOT keep this file inside the project root and never publish it on Github, Gitlab,...**
15 |
16 | # Step 3: Install the `fcm-django` App
17 | You can use any other package, or you can develope a custom app yourself, but, I prefer `fcm-django` because it is simple and single responsible!
18 | ```bash
19 | pip install fcm-django
20 | ```
21 | For communicating through API it is necessary to install Django Rest Framework:
22 | ```bash
23 | pip install djangorestframework
24 | ```
25 | Here are the package's repository on Github and its documentation:
26 | - [Github Repo](https://github.com/xtrinch/fcm-django)
27 | - [Documentation](https://fcm-django.readthedocs.io/en/latest/)
28 |
29 | # Step 4: Install the `firebase-admin` Package
30 | The `firebase-admin` is the official library from firebase that communicates with its services.
31 | ```bash
32 | pip install firebase-admin
33 | ```
34 |
35 | # Step 5: Modifying Django Settings
36 |
37 | Import `initialize_app` from `firebase_admin` in your setting file:
38 | ```python
39 | from firebase_admin import initialize_app
40 | ```
41 |
42 | Add `fcm_django` and `rest_framework` to the `INSTALLED_APPS`:
43 | ```python
44 | INSTALLED_APPS = [
45 | 'django.contrib.admin',
46 | 'django.contrib.auth',
47 | 'django.contrib.contenttypes',
48 | 'django.contrib.sessions',
49 | 'django.contrib.messages',
50 | 'django.contrib.staticfiles',
51 | # You Apps Here...
52 | 'rest_framework', # For Rest API
53 | 'fcm_django', # New
54 | ]
55 | ```
56 | Add required configs for `fcm-django` app:
57 | ```python
58 | # Optional ONLY IF you have initialized a firebase app already:
59 | # Visit https://firebase.google.com/docs/admin/setup/#python
60 | # for more options for the following:
61 | # Store an environment variable called GOOGLE_APPLICATION_CREDENTIALS
62 | # which is a path that point to a json file with your credentials.
63 | # Additional arguments are available: credentials, options, name
64 | FIREBASE_APP = initialize_app()
65 | # To learn more, visit the docs here:
66 | # https://cloud.google.com/docs/authentication/getting-started>
67 |
68 | FCM_DJANGO_SETTINGS = {
69 | # default: _('FCM Django')
70 | "APP_VERBOSE_NAME": "[string for AppConfig's verbose_name]",
71 | # true if you want to have only one active device per registered user at a time
72 | # default: False
73 | "ONE_DEVICE_PER_USER": True/False,
74 | # devices to which notifications cannot be sent,
75 | # are deleted upon receiving error response from FCM
76 | # default: False
77 | "DELETE_INACTIVE_DEVICES": True/False,
78 | }
79 | ```
80 |
81 | Run the migrate command before running the server:
82 | ```bash
83 | python manage.py migrate
84 | ```
85 |
86 | **Note:** Before running the project make sure to set an environment variable named `GOOGLE_APPLICATION_CREDENTIALS` to the path of your downloaded JSON file from firebase in Step 2.
87 |
88 | # Step 6: The Default Service Worker
89 | For handling background notifications, when user is not focused on tab, it is required to have a service worker. Just create a file named `firebase-messaging-sw.js` and put it in root, so it should be accessible by `/firebase-messaging-sw.js/` URL. In django you can put this file in `templates` and add following line of code in your root `urls.py`:
90 | ```python
91 | from django.urls import include, path
92 | from django.views.generic import TemplateView
93 |
94 | urlpatterns = [
95 | ...,
96 | path("firebase-messaging-sw.js",
97 | TemplateView.as_view(
98 | template_name="firebase-messaging-sw.js",
99 | content_type="application/javascript",
100 | ),
101 | name="firebase-messaging-sw.js"
102 | ),
103 | ...
104 | ]
105 | ```
106 |
107 | So the content of `templates/firebase-messaging-sw.js` is:
108 | ```javascript
109 | // [START initialize_firebase_in_sw]
110 | // Give the service worker access to Firebase Messaging.
111 | // Note that you can only use Firebase Messaging here, other Firebase libraries
112 | // are not available in the service worker.
113 | importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
114 | importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
115 |
116 | // Initialize the Firebase app in the service worker by passing in the
117 | // messagingSenderId.
118 | firebase.initializeApp({
119 | // Replace messagingSenderId with yours
120 | 'messagingSenderId': '504975596104'
121 | });
122 |
123 | // Retrieve an instance of Firebase Messaging so that it can handle background
124 | // messages.
125 | const messaging = firebase.messaging();
126 | // [END initialize_firebase_in_sw]
127 |
128 | // If you would like to customize notifications that are received in the
129 | // background (Web app is closed or not in browser focus) then you should
130 | // implement this optional method.
131 | // [START background_handler]
132 | messaging.setBackgroundMessageHandler(function(payload) {
133 | console.log('[firebase-messaging-sw.js] Received background message ', payload);
134 | // Customize notification here
135 | payload = payload.data;
136 | const notificationTitle = payload.title;
137 | const notificationOptions = {
138 | body: payload.body,
139 | icon: payload.icon_url,
140 | };
141 |
142 | self.addEventListener('notificationclick', function (event) {
143 | event.notification.close();
144 | clients.openWindow(payload.url);
145 | });
146 |
147 | return self.registration.showNotification(notificationTitle,
148 | notificationOptions);
149 | });
150 | // [END background_handler]
151 | ```
152 | # Step 7: Register User Devices
153 | Registering the user device can be done by some JavaScript code which communicates with Firebase API. You can write a better, cleaner and more secure version of this code yourself. This is just for educational purposes.
154 |
155 | ```html
156 |
157 |
158 |
276 | ```
277 |
278 | # Step 8: Prepare Create Device Endpoint
279 | Simply you can add following code snippet to your root `urls.py`:
280 | ```python
281 | from fcm_django.api.rest_framework import FCMDeviceAuthorizedViewSet
282 | from rest_framework.routers import DefaultRouter
283 |
284 | router = DefaultRouter()
285 | router.register('devices', FCMDeviceAuthorizedViewSet)
286 |
287 | urlpatterns = [
288 | # URLs will show up at /devices
289 | # DRF browsable API which lists all available endpoints
290 | path('api/', include(router.urls)),
291 | # ...
292 | ]
293 | ```
294 |
295 | # Step 9: Sending Messages
296 | Sending message to users is quite straightforward. First, create a `Message` object with your customized data, then send it to target devices:
297 | ```python
298 | from firebase_admin.messaging import Message
299 | from fcm_django.models import FCMDevice
300 |
301 | message_obj = Message(
302 | data={
303 | "Nick" : "Mario",
304 | "body" : "great match!",
305 | "Room" : "PortugalVSDenmark"
306 | },
307 | )
308 |
309 | # You can still use .filter() or any methods that return QuerySet (from the chain)
310 | device = FCMDevice.objects.all().first()
311 | # send_message parameters include: message, dry_run, app
312 | device.send_message(message_obj)
313 | # Boom!
314 | ```
315 |
316 | # Step 10: Important Notes
317 | - **In the sample project login and register are not the responsibility of the notifications app.** I just put it there for the sake of simplicity! So, please be careful in your real-life projects!
318 |
319 | - Users should login at least one time in order to receive notifications.
320 |
321 | - These code snippets and the sample project is just a tutorial to implement FCM in django project. You should consider your use-cases and read the documentations carefully.
322 |
323 |
324 | - Finally, if this tutorial was helpful to you, give me a star ⭐ and share it with your friends.
--------------------------------------------------------------------------------