Brunch :: 모바일 푸시 알림 디자인 때 하기 쉬운 5가지 실수
위의 글을 읽고 저번에 푸시 알람 개발을 한 것을 보면, 정말 사용자가 시도 때도 없이 울리는 알림에 고통을 겪을 것이다, 그러는 것을 방지하기 위해서 나는 그룹을 만들고, 그룹별로 보내는 기능을 만들어 보았다.
webpush 패키지에도 그룹은 있다, 하지만 나는 이해하지 못할 방식이었는데, Push Information에 그룹 한개만 넣을 수 있다는 점이었다. 여러 그룹을 작동시킬려면, PushInformation의 갯수가 아주 많아야 한다는 점이 불만이였다. 그래서 나는 django의 Group을 이용해서 User에게 보내는 로직으로 구현을 하게 되었다.
# webpush.models.py
from django.db import models
from django.core.exceptions import FieldError
from django.conf import settings
# Create your models here.
class Group(models.Model):
name = models.CharField(max_length=255, unique=True)
class SubscriptionInfo(models.Model):
browser = models.CharField(max_length=100)
endpoint = models.URLField(max_length=500)
auth = models.CharField(max_length=100)
p256dh = models.CharField(max_length=100)
class PushInformation(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='webpush_info', blank=True, null=True, on_delete=models.CASCADE)
subscription = models.ForeignKey(SubscriptionInfo, related_name='webpush_info', on_delete=models.CASCADE)
group = models.ForeignKey(Group, related_name='webpush_info', blank=True, null=True, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
# Check whether user or the group field is present
# At least one field should be present there
# Through from the functionality its not possible, just in case! ;)
if self.user or self.group:
super(PushInformation, self).save(*args, **kwargs)
else:
raise FieldError('At least user or group should be present')
일단, 이 View는 기존 카테고리 뷰에서도 보여야 하기때문에 login과 required_post 데코레이터를 포함시키고, json으로 동작하게끔 만들었다.
나는 django의 그룹을 카테고리 이름에 -PushNotification을 붙이도록 만들고, 사용자가 있는지 여부를 exist()
로 확인했고
user_set으로 더하거나 지우며 메시지로 그 내용을 확인하게끔 했다.
...
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST
from django.utils.decorators import method_decorator
from django.contrib.auth.models import Group
try:
from django.utils import simplejson as json
except ImportError:
import json
...
# views.py
# https://docs.djangoproject.com/en/3.1/topics/class-based-views/intro/#id1
@method_decorator(login_required, name='dispatch')
@method_decorator(require_POST, name='dispatch')
class PushNotificationGroupView(View):
def post(self, request, slug):
user = request.user
employee = get_object_or_404(Category, slug=self.kwargs['slug'])
group, created = Group.objects.get_or_create(name=f'{employee.__str__()}-PushNotification')
if user.groups.filter(name=group).exists():
group.user_set.remove(user)
message = f"You canceled the push notification for {employee.__str__()}."
else:
group.user_set.add(user)
message = f"You allowed the push notification for {employee.__str__()}."
context = {'message' : message}
return HttpResponse(json.dumps(context), content_type='application/json')
urls.py도 기존의 카테고리 아래에 /push를 붙여 동작하도록 하였다.
urls.py
urlpatterns = [
...
path("category/<unislug:slug>/", views.CategoryDetailView.as_view(), name="category_detail"),
path("category/<unislug:slug>/push", views.PushNotificationGroupView.as_view(), name="push_group_set"),
...
]
템플릿에 경우에는 버튼을 만들고, jquery의 ajax로 그 내용을 보내는 것으로 지정하였다.
<div class="container justify-content-center w-auto mr-auto" style="padding-top: 10px">
<button type="button" id="notifi" class="btn btn-outline-dark" name="{{ category.slug }}">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-bell-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M8 16a2 2 0 0 0 2-2H6a2 2 0 0 0 2 2zm.995-14.901a1 1 0 1 0-1.99 0A5.002 5.002 0 0 0 3 6c0 1.098-.5 6-2 7h14c-1.5-1-2-5.902-2-7 0-2.42-1.72-4.44-4.005-4.901z"/>
</svg>
</button>
</div>
...
...
<script>
$('#notifi').click(function(){
{% if user.is_authenticated %}
var slug = $(this).attr('name') // 클릭한 요소의 attribute 중 name의 값을 가져온다.
$.ajax({
type: "POST", // 데이터를 전송하는 방법을 지정한다.
url: "{{ category.get_absolute_url }}push", // 통신할 url을 지정한다.
data: {'slug': slug, 'csrfmiddlewaretoken': '{{ csrf_token }}'}, // 서버로 데이터를 전송할 때 이 옵션을 사용한다.
dataType: "json", // 서버측에서 전송한 데이터를 어떤 형식의 데이터로서 해석할 것인가를 지정한다. 없으면 알아서 판단한다.
// 서버측에서 전송한 데이터 views.py like 메소드
// context = {'likes_count' : memo.total_likes, 'message' : message}
// json.dump(context)를 통해서 json 형식으로 전달된다.
success: function(response){ // 성공했을 때 호출할 콜백을 지정한다.
alert(response.message);
},
error:function(request,status,error){
alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error);
}
});
{% else %}
alert("푸시 알림 설정은 로그인 후 이용 가능합니다.");
{% endif %}
})
</script>
episode_webpush 메소드에도 차이가 생겼다. 여기에서는 카테고리 이름에 맞는 그룹을 찾고, 그 그룹에 있는 모든 user들에게 알림을 보내는 방식이다. 또한, webpush로 동작 로직을 변경할 수 있도록, webpush의 그룹을 생성하기도 하였다.
from django.contrib.auth import get_user_model
from webpush import send_user_notification
from webpush.utils import send_to_subscription
from webpush import send_group_notification
import webpush
from django.utils import formats
from django.utils.dateformat import format, time_format
from django.template.defaultfilters import date
# from django.contrib.auth.models import Group, Permission
import django.contrib.auth.models
from django.contrib.contenttypes.models import ContentType
try:
from django.utils import simplejson as json
except ImportError:
import json # https://github.com/safwanrahman/django-webpush/issues/71
def episode_webpush(episode):
episode_date = date(episode.modify_date, "Y년 m월 d일 G:i")
payload = {"head": f"{episode.__str__()}이 업데이트 되었습니다.", "body": f"{episode.__str__()} 업데이트 일시 : {episode_date}",
"icon": "https://i.imgur.com/..., "url": f"{episode.get_absolute_url()}"}
django_group, created = django.contrib.auth.models.Group.objects.get_or_create(
name=f'{episode.category.__str__()}-PushNotification')
webpush_group, created = webpush.models.Group.objects.get_or_create(
name=f'{episode.category.__str__()}-PushNotification')
# payload = json.dumps(payload)
users = django_group.user_set.all()
print(payload)
for user in users:
send_user_notification(user, payload=payload, ttl=1800)
한번 webpush를 시도해보자