Search code examples
djangodjango-modelsdjango-rest-frameworkdjango-channels

How to order messages based on the time its send like in normal chats?


Hey guys I am having trouble with ordering the messages based on the timestamp like in normal chats. How to do it? can anyone give me a proper solution? I have updated the question with the model manager I use.

This is the model.

class Chat(models.Model):
    first_user          = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='first_user_chat', null=True)
    second_user         = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='second_user_chat', null=True)
    timestamp           = models.DateTimeField(auto_now_add=True)

class ChatMessage(models.Model):
    chat                = models.ForeignKey(Chat, blank=True, null=True, on_delete=models.SET_NULL)
    user                = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sender', on_delete=models.CASCADE)
    message             = models.TextField()
    # read              = models.BooleanField(blank=False, null=True)
    timestamp           = models.DateTimeField(auto_now_add=True)

views

class ChatView(LoginRequiredMixin, FormMixin, DetailView):
template_name = 'messenger/chat.html'
form_class = ComposeForm
success_url = './'

def get_queryset(self):
    return Chat.objects.by_user(self.request.user).order_by('-timestamp')

def get_object(self):
    other_username  = self.kwargs.get("username")
    obj, created    = Chat.objects.get_or_new(self.request.user, other_username)
    if obj == None:
        raise Http404
    return obj

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    context['messages'] = ChatMessage.objects.filter(user=self.request.user).order_by('-timestamp')
    context['form'] = self.get_form()
    return context

def post(self, request, *args, **kwargs):
    if not self.request.user.is_authenticated:
        return HttpResponseForbidden()
    self.object = self.get_object()
    form = self.get_form()
    if form.is_valid():
        return self.form_valid(form)
    else:
        return self.form_invalid(form)

def form_valid(self, form):
    chat = self.get_object()
    user = self.request.user
    message = form.cleaned_data.get("message")
    ChatMessage.objects.create(user=user, chat=chat, message=message)
    return super().form_valid(form)

This is the template I use to render this. This also has all the javascript codes.

{% load static %}

{% static "channels/js/websocketbridge.js" %}

{% block content %}

        <a class="btn btn-light" href="{% url 'messenger:inboxview' %}">Back to Inbox</a>

    <div class="msg_history offset-md-1" id="chat-items">
        {% for chat in object.chatmessage_set.all %}
          {% if chat.user == user %}
          <div class="outgoing_msg">
            <div class="outgoing_msg_img"> 
                <img class="rounded-circle" style="height: 30px; width: 30px; margin-right: 16px;" 
                src="{{ chat.user.profile.profile_pic.url }}">
            </div>
            <div class="sent_msg">
              <p>
                <div>
                  {{ chat.message }}
                </div>
                <small><span style="font-size: 6" class="time_date"> {{ chat.timestamp }}</span></small>
              </p>
            </div>
          </div>
          {% else %}
          <div class="incoming_msg offset-md-8">
            <div class="incoming_msg_img">
                <img class="rounded-circle offset-md-7" style="height: 30px; width: 30px; margin-right: 16px;" src="{{ chat.user.profile.profile_pic.url }}">
            </div>
            <div class="received_msg">
              <div class="received_withd_msg">
                <p>
                  <div>
                    {{ chat.message }}
                  </div>
                  <small><span style="font-size: 6" class="time_date"> {{ chat.timestamp }}</span></small>
              </p>
              </div>
            </div>
          </div>
          {% endif %}
        {% endfor %}
        </div>

        <div class="type_msg offset-md-1">
          <div class="input_msg_write">
            <!-- text input / write message form -->
            <form id='form' method='POST'>
              {% csrf_token %}
              <input type='hidden' id='myUsername' value='{{ user.username }}' />
              {{ form.as_p }}
              <center><button type="submit" class='btn btn-success disabled' value="Send">Send</button></center>
            </form>
          </div>
        </div>



{% block javascript %}
<script src='https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js'></script>

<script type="text/javascript">
// websocket scripts - client side*
console.log(window.location)
  var loc = window.location
  var formData = $("#form")
  var msgInput = $("#id_message")
  var chatHolder = $('#chat-items')
  var me = $('#myUsername').val()

  var wsStart = 'ws://'
  if (loc.protocol == 'https:') {
    wsStart = 'wss://'
  }
  var endpoint = wsStart + loc.host + loc.pathname
  var socket = new ReconnectingWebSocket(endpoint)

  // below is the message I am receiving
  socket.onmessage = function(e) {
    console.log("message", e)
    var data = JSON.parse(event.data);
    // Find the notification icon/button/whatever and show a red dot, add the notification_id to element as id or data attribute.
    var chatDataMsg = JSON.parse(e.data)
    chatHolder.append('<li>' + chatDataMsg.message + '  from  ' + ' - ' + chatDataMsg.username + '</li>')
  }
  // below is the message I am sending
  socket.onopen = function(e) {
    console.log("open", e)
    formData.submit(function(event) {
      event.preventDefault()
      var msgText = msgInput.val()

      var finalData = {
        'message': msgText
      }
      socket.send(JSON.stringify(finalData))
      formData[0].reset()
    })
  }
  socket.onerror = function(e) {
    console.log("error", e)
  }
  socket.onclose = function(e) {
    console.log("close", e)
  }
</script>

<script>
  document.addEventListener('DOMContentLoaded', function() {
    const webSocketBridge = new channels.WebSocketBridge();
    webSocketBridge.connect('/ws');
    webSocketBridge.listen(function(action, stream) {
      console.log("RESPONSE:", action);
    })
    document.ws = webSocketBridge; /* for debugging */
  })

</script>
{% endblock %}

{% endblock %}

Please do let me know how to properly do it.

Thank you!


Solution

  • please try this:

    in models.py add class Meta:

    class ChatMessage(models.Model):
        chat                = models.ForeignKey(Chat, blank=True, null=True, on_delete=models.SET_NULL)
        user                = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sender', on_delete=models.CASCADE)
        message             = models.TextField()
        # read              = models.BooleanField(blank=False, null=True)
        timestamp           = models.DateTimeField(auto_now_add=True)
    
        class Meta:
            ordering = ["-timestamp"]
    

    if this works it is because in the template you are iterating like this: {% for chat in object.chatmessage_set.all %} is not ordered.

    Another thing that i see is that you are not using this: context['messages'] = ChatMessage.objects.filter(user=self.request.user).order_by('-timestamp') but i would change with something like this:

    chat = self.get_object()
    context["messages"] = chat.chatmessage_set.all().order_by("-timestamp")
    

    and use that in template to iterate instead of object.chatmessage_set.all