Почему функция обработчика событий в consumer.py вызывается несколько раз, а затем соединение с веб-сокетом принудительно прерывается? - PullRequest
2 голосов
/ 30 октября 2019

Я создал уведомитель в реальном времени , используя веб-сокет, реализованный с использованием Django-Channels. Он отправляет «автору сообщения» уведомление в режиме реального времени всякий раз, когда любому пользователю нравится сообщение автора .

проблема заключается в том, что при однократном нажатии кнопки в базе данных сохраняется несколько уведомлений . Это связано с тем, что функция (like_notif ()) определена в consumer.py , которая сохраняет данные уведомлений в базе данных, когда происходит событие 'click button' вызывается несколько раз (когда он должен вызываться только один раз ) до тех пор, пока веб-сокет не будет отключен.

В следующих строках я подробно расскажу о том, что на самом делепроисходит (насколько я понимаю) за сценой.
1. Нажата кнопка «Мне нравится».

<a class="like-btn" id="like-btn-{{ post.pk }}" data-likes="{{ post.likes.count }}" href="{% url 'like_toggle' post.slug %}">
    Like
</a>
JavaScript предотвращает действие по умолчанию и генерирует AJAX-вызов URL-адреса.
$('.like-btn').click(function (event) {
   event.preventDefault();
   var this_ = $(this);
   var url = this_.attr('href');
   var likesCount = parseInt(this_.attr('data-likes')) || 0;

   $.ajax({
       url: url,
       method: "GET",
       data: {},
       success: function (json) {
          // DOM is manipulated accordingly.
       },
       error: function (json) {
           // Error.
       }
   });
});
URL-адрес сопоставлен с функцией, определенной в views.py. (Примечание: код для Почтовая модель является последним, если требуется для справки.)
# In urls.py

urlpatterns = [
    path('<slug:slug>/like/', views.post_like_toggle, name="like_toggle"),
]
# In views.py

@login_required
def post_like_toggle(request, slug):
    """
        Function to like/unlike posts using AJAX.
    """
    post = Post.objects.get(slug=slug)
    user = request.user

    if user in post.likes.all():
        # Like removed 
        post.likes.remove(user)
    else:
        # Like Added
        post.likes.add(user)
        user_profile = get_object_or_404(UserProfile, user=user)

        # If Post author is not same as user liking the post
        if str(user_profile.user) != str(post.author):

            # Sending notification to post author is the post is liked.
            channel_layer = get_channel_layer()

            text_dict = {
                # Contains some data in JSON format to be sent.
            }

            async_to_sync(channel_layer.group_send)(
                "like_notif", {
                    "type": "notif_like",
                    "text": json.dumps(text_dict),
                }
            )

    response_data = {
        # Data sent to AJAX success/error function.
    }

    return JsonResponse(response_data)

Потребитель создан для обработки этого в consumer.py.
# In consumers.py

class LikeNotificationConsumer(AsyncConsumer):

    async def websocket_connect(self, event):
        print("connect", event)
        await self.channel_layer.group_add("like_notif", self.channel_name)
        await self.send({
            "type": "websocket.accept",
        })

    async def websocket_receive(self, event):
        print("receive", event)

    async def websocket_disconnect(self, event):
        print("disconnect", event)
        await self.channel_layer.group_discard("like_notif", self.channel_name)

    async def notif_like(self, event):
        print("like_notif_send", event)

        await self.send({
            "type": "websocket.send",
            "text": event.get('text')
        })

        json_dict = json.loads(event.get('text'))

        recipient_username = json_dict.get('recipient_username')
        recipient = await self.get_user(recipient_username)

        sender_username = json_dict.get('sender_username')
        sender = await self.get_user(sender_username)

        post_pk = json_dict.get('post_pk', None)
        post = await self.get_post(post_pk)

        verb = json_dict.get('verb')
        description = json_dict.get('description', None)
        data_dict = json_dict.get('data', None)
        data = json.dumps(data_dict)

        await self.create_notif(recipient, sender, verb, post, description, data)

    @database_sync_to_async
    def get_user(self, username_):
        return User.objects.get(username=username_)

    @database_sync_to_async
    def get_post(self, pk_):
        return Post.objects.get(pk=pk_)

    @database_sync_to_async
    def create_notif(self, recipient, sender, verb, post=None, description=None, data=None, *args, **kwargs):
        return Notification.objects.create(recipient=recipient, sender=sender,  post=post, verb=verb, description=description,  data=data)

Примечание: notif_like () функция вызывается несколько раз и, таким образом,данные сохраняются несколько раз в базе данных. Почему эта функция вызывается несколько раз?

JavaScript-код (написанный в HTML-файле), обрабатывающий соединение с веб-сокетом. Я использовал Переподключение Websockets .
<script type="text/javascript">

    var loc = window.location
    var wsStart = 'ws://' 
    if(loc.protocol == 'https:') {
        wsStart = 'wss://' 
    }
    var endpoint = wsStart + loc.host + "/like_notification/"
    var likeSocket = new ReconnectingWebSocket(endpoint)

    likeSocket.onmessage = function (e) {
        console.log("message LikeNotificationConsumer", e)
        var data_dict = JSON.parse(e.data)

        /*
            DOM manipulation using data from data_dict.
            NOTE: All the intended data is coming through perfectly fine.
        */

    };

    likeSocket.onopen = function (e) {
        console.log("opened LikeNotificationConsumer", e)
    }

    likeSocket.onerror = function (e) {
        console.log("error LikeNotificationConsumer", e)
    }

    likeSocket.onclose = function (e) {
        console.log("close LikeNotificationConsumer", e)
    }
</script>
Почтовая модель для справки
# In models.py

class Post(models.Model):

    title = models.CharField(max_length=255, blank=False, null=True, verbose_name='Post Title')
    post_content = RichTextUploadingField(blank=False, null=True, verbose_name='Post Content')
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        default=None,
        blank=True,
        null=True
    )
    likes = models.ManyToManyField(User, blank=True, related_name='post_likes')
    published = models.DateTimeField(default=datetime.now, blank=True)
    tags = models.ManyToManyField(Tags, verbose_name='Post Tags', blank=True, default=None)
    slug = models.SlugField(default='', blank=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title + str(self.pk))
        super(Post, self).save()

    def get_like_url(self):
        return reverse("like_toggle", (), {'slug', self.slug})

    def __str__(self):
        return '%s' % self.title
Модель уведомления для справки
# In models.py

class Notification(models.Model):

    recipient = models.ForeignKey(User, blank=False, on_delete=models.CASCADE, related_name="Recipient",)
    sender = models.ForeignKey(
        User,
        blank=False,
        on_delete=models.CASCADE,
        related_name="Sender"
    )

    # Post (If notification is attached to some post)
    post = models.ForeignKey(
        Post,
        blank=True,
        default=None,
        on_delete=models.CASCADE, 
        related_name="Post",
    )
    verb = models.CharField(max_length=255)
    description = models.TextField(blank=True, null=True)
    data = models.TextField(blank=True, null=True)
    timestamp = models.DateTimeField(default=timezone.now)
    unread = models.BooleanField(default=True, blank=False, db_index=True)

    def __str__(self):
        return self.verb
Настройка слоя каналов.
# In settings.py

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer", 
        "CONFIG": {
            "hosts": [("localhost", 6379)]
        }
    }
}

Я ожидаю, что уведомление будет сохранено только один раз за нажатие кнопки «Нравится», но оно сохраняется несколько раз как функция notif_like () , определенная в consumer.py , вызывается несколько раз за клик.

Кроме того, веб-сокет отключается внезапно после того, как эта и следующая ошибка отображаются в терминале -

Application instance <Task pending coro=<SessionMiddlewareInstance.__call__() running at /home/pirateksh/DjangoProjects/ccwebsite/ccenv/lib/python3.7/site-packages/channels/sessions.py:183> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7fb7603af1c8>()]>> for connection <WebSocketProtocol client=['127.0.0.1', 52114] path=b'/like_notification/'> took too long to shut down and was killed.

Я уже давно борюсь с этим, так что будет действительно полезно, если кто-то может помочь мне в этом.

...