Browse Source

More accurate online tracking #76

Ralfp 12 years ago
parent
commit
d6e829423d

+ 0 - 1
misago/apps/admin/online/forms.py

@@ -9,7 +9,6 @@ class SearchSessionsForm(Form):
     type = forms.ChoiceField(choices=(
     type = forms.ChoiceField(choices=(
                                       ('all', _("All types")),
                                       ('all', _("All types")),
                                       ('registered', _("Registered Members Sessions")),
                                       ('registered', _("Registered Members Sessions")),
-                                      ('hidden', _("Hidden Sessions")),
                                       ('guest', _("Guests Sessions")),
                                       ('guest', _("Guests Sessions")),
                                       ('crawler', _("Crawler Sessions")),
                                       ('crawler', _("Crawler Sessions")),
                                       ), required=False)
                                       ), required=False)

+ 16 - 11
misago/apps/index.py

@@ -36,27 +36,32 @@ def index(request):
             ranks_list.append(rank_entry)
             ranks_list.append(rank_entry)
             ranks_dict[rank.pk] = rank_entry
             ranks_dict[rank.pk] = rank_entry
         if ranks_dict:
         if ranks_dict:
-            for session in Session.objects.select_related('user').filter(rank__in=ranks_dict.keys()).filter(last__gte=timezone.now() - timedelta(minutes=10)).filter(user__isnull=False):
+            for session in Session.objects.select_related('user').filter(rank__in=ranks_dict.keys()).filter(last__gte=timezone.now() - timedelta(seconds=request.settings['sessions_tracker_sync_frequency'])).filter(user__isnull=False):
                 if not session.user_id in users_list:
                 if not session.user_id in users_list:
                     ranks_dict[session.user.rank_id]['online'].append(session.user)
                     ranks_dict[session.user.rank_id]['online'].append(session.user)
                     users_list.append(session.user_id)
                     users_list.append(session.user_id)
             # Assert we are on list
             # Assert we are on list
             if (request.user.is_authenticated() and request.user.rank_id in ranks_dict.keys()
             if (request.user.is_authenticated() and request.user.rank_id in ranks_dict.keys()
-                and not request.user.id in users_list):
+                and not request.user.pk in users_list):
                     ranks_dict[request.user.rank_id]['online'].append(request.user)
                     ranks_dict[request.user.rank_id]['online'].append(request.user)
+                    users_list.append(request.user.pk)
+            cache.set('team_users_online', users_list, request.settings['sessions_tracker_sync_frequency'])
             del ranks_dict
             del ranks_dict
             del users_list
             del users_list
-        cache.set('ranks_online', ranks_list, 300)
+        cache.set('ranks_online', ranks_list, request.settings['sessions_tracker_sync_frequency'])
 
 
+    print cache.get('team_users_online')
     # Users online
     # Users online
-    users_online = cache.get('users_online', 'nada')
-    if users_online == 'nada':
-        users_online = Session.objects.filter(matched=True).filter(crawler__isnull=True).filter(last__gte=timezone.now() - timedelta(seconds=300)).count()
-        cache.set('users_online', users_online, 300)
-    if not users_online and not request.user.is_crawler():
-        # Cheatey trick to make sure we'll never display
-        # zero users online to human client
-        users_online = 1
+    users_online = {
+                    'members': request.onlines.members(),
+                    'all': request.onlines.all(),
+                   }
+    if not users_online['members'] and request.user.is_authenticated():
+        users_online['members'] += 1
+    if users_online['members'] > users_online['all']:
+        users_online['all'] = users_online['members']
+    if users_online['members'] >= users_online['all'] and request.user.is_anonymous():
+        users_online['all'] += 1
 
 
     # Load reads tracker and build forums list
     # Load reads tracker and build forums list
     reads_tracker = ForumsTracker(request.user)
     reads_tracker = ForumsTracker(request.user)

+ 6 - 0
misago/apps/signin/views.py

@@ -1,3 +1,4 @@
+from django.core.cache import cache
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.shortcuts import redirect
 from django.shortcuts import redirect
 from django.template import RequestContext
 from django.template import RequestContext
@@ -106,4 +107,9 @@ def signout(request):
     request.messages.set_flash(Message(_("You have been signed out.")), 'info', 'security')
     request.messages.set_flash(Message(_("You have been signed out.")), 'info', 'security')
     if request.firewall.admin:
     if request.firewall.admin:
         return redirect(reverse(site.get_admin_index()))
         return redirect(reverse(site.get_admin_index()))
+    else:
+        request.onlines.sign_out()
+        if user.pk in cache.get('team_users_online', []):
+            print 'SYNC TEH ONLINEZ'
+            cache.delete_many(['team_users_online', 'ranks_online'])
     return redirect(reverse('index'))
     return redirect(reverse('index'))

+ 2 - 1
misago/auth.py

@@ -120,4 +120,5 @@ def sign_user_in(request, user):
                         )
                         )
     user.save(force_update=True)
     user.save(force_update=True)
     request.session.set_user(user)
     request.session.set_user(user)
-    request.session.set_hidden(user.hide_activity > 0)
+    if not request.firewall.admin:
+        request.onlines.sign_in()

+ 2 - 1
misago/fixtures/accountssetings.py

@@ -8,9 +8,10 @@ settings_fixture = (
         'description': _("Those settings allow you to increase security of your members accounts."),
         'description': _("Those settings allow you to increase security of your members accounts."),
         'settings': (
         'settings': (
             ('account_activation', {
             ('account_activation', {
+                'value':        "none",
                 'type':         "string",
                 'type':         "string",
                 'input':        "choice",
                 'input':        "choice",
-                'extra':        {'choices': [('', _("No validation required")), ('user', _("Activation Token sent to User")), ('admin', _("Activation by Administrator")), ('block', _("Dont allow new registrations"))]},
+                'extra':        {'choices': [('none', _("No validation required")), ('user', _("Activation Token sent to User")), ('admin', _("Activation by Administrator")), ('block', _("Dont allow new registrations"))]},
                 'separator':    _("Users Registrations"),
                 'separator':    _("Users Registrations"),
                 'name':         _("New accounts validation"),
                 'name':         _("New accounts validation"),
             }),
             }),

+ 10 - 0
misago/fixtures/onlinemonitor.py

@@ -0,0 +1,10 @@
+from misago.utils.fixtures import load_monitor_fixture
+
+monitor_fixture = {
+                   'online_members': 0,
+                   'online_all': 0,
+                  }
+
+
+def load():
+    load_monitor_fixture(monitor_fixture)

+ 8 - 0
misago/fixtures/signingsettings.py

@@ -15,6 +15,14 @@ settings_fixture = (
                 'name':         _("Check IP on session authorization"),
                 'name':         _("Check IP on session authorization"),
                 'description':  _("Makes sessions more secure, but can cause problems with proxies and VPN's."),
                 'description':  _("Makes sessions more secure, but can cause problems with proxies and VPN's."),
             }),
             }),
+            ('sessions_tracker_sync_frequency', {
+                'value':        300,
+                'type':         "integer",
+                'input':        "text",
+                'extra':         {'min': 15},
+                'name':         _("Online Tracker Updates Frequency"),
+                'description':  _("How often do you want online tracker to synchronize itself with database? Low numbers provide good accuracy at cost of database traffic while great number provides your users with general idea how many are currently online while at same time keeping stress off your database."),
+            }),
             ('remember_me_allow', {
             ('remember_me_allow', {
                 'value':        True,
                 'value':        True,
                 'type':         "boolean",
                 'type':         "boolean",

+ 0 - 3
misago/middleware/session.py

@@ -12,9 +12,6 @@ class SessionMiddleware(object):
             request.session = HumanSession(request)
             request.session = HumanSession(request)
             request.user = request.session.get_user()
             request.user = request.session.get_user()
 
 
-            if request.user.is_authenticated():
-                request.session.set_hidden(request.user.hide_activity > 0)
-
     def process_response(self, request, response):
     def process_response(self, request, response):
         try:
         try:
             # Sync last visit date
             # Sync last visit date

+ 14 - 0
misago/middleware/user.py

@@ -2,6 +2,7 @@ from django.conf import settings
 from django.utils import timezone
 from django.utils import timezone
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from misago.messages import Message
 from misago.messages import Message
+from misago.onlines import MembersOnline
 
 
 def set_timezone(new_tz):
 def set_timezone(new_tz):
     if settings.USE_TZ:
     if settings.USE_TZ:
@@ -14,6 +15,11 @@ def set_timezone(new_tz):
 
 
 class UserMiddleware(object):
 class UserMiddleware(object):
     def process_request(self, request):
     def process_request(self, request):
+        request.onlines = MembersOnline(request.monitor, request.settings['sessions_tracker_sync_frequency'])
+
+        if request.session.created() and not request.firewall.admin:
+            request.onlines.new_session()
+
         if request.user.is_authenticated():
         if request.user.is_authenticated():
             # Set user timezone and rank
             # Set user timezone and rank
             request.session.rank = request.user.rank_id
             request.session.rank = request.user.rank_id
@@ -21,8 +27,16 @@ class UserMiddleware(object):
 
 
             # Display "welcome back!" message
             # Display "welcome back!" message
             if request.session.remember_me:
             if request.session.remember_me:
+                request.onlines.sign_in()
                 request.messages.set_message(Message(_("Welcome back, %(username)s! We've signed you in automatically for your convenience.") % {'username': request.user.username}), 'info')
                 request.messages.set_message(Message(_("Welcome back, %(username)s! We've signed you in automatically for your convenience.") % {'username': request.user.username}), 'info')
         else:
         else:
             # Set guest's timezone and empty rank
             # Set guest's timezone and empty rank
             set_timezone(request.settings['default_timezone'])
             set_timezone(request.settings['default_timezone'])
             request.session.rank = None
             request.session.rank = None
+
+    def process_response(self, request, response):
+        try:
+            request.onlines.sync()
+        except AttributeError:
+            pass
+        return response

+ 0 - 1
misago/models/sessionmodel.py

@@ -13,7 +13,6 @@ class Session(models.Model):
     rank = models.ForeignKey('Rank', related_name='sessions', null=True, on_delete=models.SET_NULL)
     rank = models.ForeignKey('Rank', related_name='sessions', null=True, on_delete=models.SET_NULL)
     admin = models.BooleanField(default=False)
     admin = models.BooleanField(default=False)
     matched = models.BooleanField(default=False)
     matched = models.BooleanField(default=False)
-    hidden = models.BooleanField(default=False)
 
 
     class Meta:
     class Meta:
         app_label = 'misago'
         app_label = 'misago'

+ 2 - 3
misago/models/usermodel.py

@@ -358,11 +358,10 @@ class User(models.Model):
         self.password_date = tz_util.now()
         self.password_date = tz_util.now()
         self.password = make_password(raw_password.strip())
         self.password = make_password(raw_password.strip())
 
 
-    def set_last_visit(self, ip, agent, hidden=False):
+    def set_last_visit(self, ip, agent):
         self.last_date = tz_util.now()
         self.last_date = tz_util.now()
         self.last_ip = ip
         self.last_ip = ip
         self.last_agent = agent
         self.last_agent = agent
-        self.last_hide = hidden
 
 
     def check_password(self, raw_password, mobile=False):
     def check_password(self, raw_password, mobile=False):
         """
         """
@@ -553,7 +552,7 @@ class Crawler(Guest):
         self.username = username
         self.username = username
 
 
     def is_anonymous(self):
     def is_anonymous(self):
-        return True
+        return False
 
 
     def is_authenticated(self):
     def is_authenticated(self):
         return False
         return False

+ 4 - 0
misago/monitor.py

@@ -1,3 +1,4 @@
+from datetime import timedelta
 from django.core.cache import cache
 from django.core.cache import cache
 from django.utils import timezone
 from django.utils import timezone
 from misago.models import MonitorItem
 from misago.models import MonitorItem
@@ -42,6 +43,9 @@ class Monitor(object):
             return self._items[key][1]
             return self._items[key][1]
         return None
         return None
 
 
+    def expired(self, key, seconds=5):
+        return self._items[key][1] < (timezone.now() - timedelta(seconds=seconds))
+
     def has_key(self, key):
     def has_key(self, key):
         return key in self._items
         return key in self._items
 
 

+ 38 - 0
misago/onlines.py

@@ -0,0 +1,38 @@
+from datetime import timedelta
+from django.utils import timezone
+from misago.models import Session
+
+class MembersOnline(object):
+    def __init__(self, monitor, frequency=180):
+        self.monitor = monitor
+        self.frequency = frequency
+        self._members = int(monitor['online_members'])
+        self._all = int(monitor['online_all'])
+        self._om = self._members
+        self._oa = self._all
+        if monitor.expired('online_all', frequency):
+            queryset = Session.objects.filter(matched=True).filter(crawler__isnull=True).filter(last__gte=timezone.now() - timedelta(seconds=frequency))
+            self._all = queryset.count()
+            self._members = queryset.filter(user__isnull=False).count()
+
+    def new_session(self):
+        self._all += 1
+
+    def sign_in(self):
+        self._members += 1
+
+    def sign_out(self):
+        if self._members:
+            self._members -= 1
+
+    def all(self):
+        return self._all
+
+    def members(self):
+        return self._members
+
+    def sync(self):
+        if self._members != self._om:
+            self.monitor['online_members'] = self._members
+        if self._all != self._oa:
+            self.monitor['online_all'] = self._all

+ 13 - 20
misago/sessions.py

@@ -41,6 +41,12 @@ class MisagoSession(SessionBase):
         """We use sessions to track onlines so sorry, only sessions cleaner may delete sessions"""
         """We use sessions to track onlines so sorry, only sessions cleaner may delete sessions"""
         pass
         pass
 
 
+    def created(self):
+        try:
+            return self.started
+        except AttributeError:
+            return False
+
     def flush(self):
     def flush(self):
         """We use sessions to track onlines so sorry, only sessions cleaner may delete sessions"""
         """We use sessions to track onlines so sorry, only sessions cleaner may delete sessions"""
         pass
         pass
@@ -51,12 +57,6 @@ class MisagoSession(SessionBase):
     def session_expired(self):
     def session_expired(self):
         return False
         return False
 
 
-    def get_hidden(self):
-        return False
-
-    def set_hidden(self, hidden=False):
-        pass
-
     def get_ip(self, request):
     def get_ip(self, request):
         return request.META.get('HTTP_X_FORWARDED_FOR', '') or request.META.get('REMOTE_ADDR')
         return request.META.get('HTTP_X_FORWARDED_FOR', '') or request.META.get('REMOTE_ADDR')
 
 
@@ -83,7 +83,7 @@ class CrawlerSession(MisagoSession):
     Crawler Session controller
     Crawler Session controller
     """
     """
     def __init__(self, request):
     def __init__(self, request):
-        self.hidden = False
+        self.started = False
         self.team = False
         self.team = False
         self._ip = self.get_ip(request)
         self._ip = self.get_ip(request)
         self._session_key = md5('%s-%s' % (request.user.username, self._ip)).hexdigest()
         self._session_key = md5('%s-%s' % (request.user.username, self._ip)).hexdigest()
@@ -123,8 +123,8 @@ class HumanSession(MisagoSession):
     Human Session controller
     Human Session controller
     """
     """
     def __init__(self, request):
     def __init__(self, request):
+        self.started = False
         self.expired = False
         self.expired = False
-        self.hidden = False
         self.team = False
         self.team = False
         self.rank = None
         self.rank = None
         self.remember_me = None
         self.remember_me = None
@@ -153,16 +153,18 @@ class HumanSession(MisagoSession):
                 self.expired = True
                 self.expired = True
                 raise IncorrectSessionException()
                 raise IncorrectSessionException()
             
             
-            # Change session to matched and extract session user and hidden flag
+            # Change session to matched and extract session user
+            if not self._session_rk.matched:
+                self.started = True
             self._session_rk.matched = True
             self._session_rk.matched = True
             self._user = self._session_rk.user
             self._user = self._session_rk.user
-            self.hidden = self._session_rk.hidden
             self.team = self._session_rk.team
             self.team = self._session_rk.team
         except (Session.DoesNotExist, IncorrectSessionException):
         except (Session.DoesNotExist, IncorrectSessionException):
             # Attempt autolog
             # Attempt autolog
             try:
             try:
                 self.remember_me = auth_remember(request, self.get_ip(request))
                 self.remember_me = auth_remember(request, self.get_ip(request))
                 self.create(request, user=self.remember_me.user)
                 self.create(request, user=self.remember_me.user)
+                self.started = True
             except AuthException as e:
             except AuthException as e:
                 # Autolog failed
                 # Autolog failed
                 self.create(request)
                 self.create(request)
@@ -194,8 +196,7 @@ class HumanSession(MisagoSession):
                     # Update user data
                     # Update user data
                     user.set_last_visit(
                     user.set_last_visit(
                                         self.get_ip(request),
                                         self.get_ip(request),
-                                        request.META.get('HTTP_USER_AGENT', ''),
-                                        hidden=self.hidden
+                                        request.META.get('HTTP_USER_AGENT', '')
                                         )
                                         )
                     user.save(force_update=True)
                     user.save(force_update=True)
                 break
                 break
@@ -205,7 +206,6 @@ class HumanSession(MisagoSession):
 
 
     def save(self):
     def save(self):
         self._session_rk.user = self._user
         self._session_rk.user = self._user
-        self._session_rk.hidden = self.hidden
         self._session_rk.team = self.team
         self._session_rk.team = self.team
         self._session_rk.rank_id = self.rank
         self._session_rk.rank_id = self.rank
         super(HumanSession, self).save()
         super(HumanSession, self).save()
@@ -233,18 +233,11 @@ class HumanSession(MisagoSession):
                         if len(request.COOKIES[cookie_token]) > 0:
                         if len(request.COOKIES[cookie_token]) > 0:
                             Token.objects.filter(id=request.COOKIES[cookie_token]).delete()
                             Token.objects.filter(id=request.COOKIES[cookie_token]).delete()
                         request.cookiejar.delete('TOKEN')
                         request.cookiejar.delete('TOKEN')
-                self.hidden = False
                 self._user = None
                 self._user = None
                 request.user = Guest()
                 request.user = Guest()
         except AttributeError:
         except AttributeError:
             pass
             pass
 
 
-    def get_hidden(self):
-        return self.hidden
-
-    def set_hidden(self, hidden=False):
-        self.hidden = hidden
-
 
 
 class SessionMock(object):
 class SessionMock(object):
     def get_ip(self, request):
     def get_ip(self, request):

+ 1 - 1
templates/cranefly/index.html

@@ -133,7 +133,7 @@
           <span class="tooltip-top" title="{% trans %}Members{% endtrans %}"><i class="icon-user"></i> {{ monitor.users|int|intcomma }}</span>
           <span class="tooltip-top" title="{% trans %}Members{% endtrans %}"><i class="icon-user"></i> {{ monitor.users|int|intcomma }}</span>
         </li>
         </li>
         <li>
         <li>
-          <span class="tooltip-top" title="{% trans %}Online{% endtrans %}"><i class="icon-map-marker"></i> {{ users_online|int|intcomma }}</span>
+          <span class="tooltip-top" title="{% trans %}Online{% endtrans %}"><i class="icon-map-marker"></i> {{ users_online.members|int|intcomma }} <span class="muted">{{ users_online.all|int|intcomma }}</spam></span>
         </li>
         </li>
       </ul>
       </ul>
     </div>
     </div>