Commit f8cb238777b5a8945c115fa5c3d108cdf2e773ec

Authored by Alexandre A. Barbosa
2 parents 71357f92 4674eafd

Merge pull request #86 from colab/paginate-threads

Paginate threads
colab/accounts/utils/mailman.py
@@ -164,4 +164,5 @@ def extract_listname_from_list(lists): @@ -164,4 +164,5 @@ def extract_listname_from_list(lists):
164 try: 164 try:
165 return [mlist.get('listname') for mlist in lists] 165 return [mlist.get('listname') for mlist in lists]
166 except ValueError: 166 except ValueError:
  167 + LOGGER.exception('listname not available')
167 return [] 168 return []
colab/super_archives/models.py
@@ -88,6 +88,8 @@ class MailingList(models.Model): @@ -88,6 +88,8 @@ class MailingList(models.Model):
88 last_imported_index = models.IntegerField(default=0) 88 last_imported_index = models.IntegerField(default=0)
89 is_private = models.BooleanField(default=False) 89 is_private = models.BooleanField(default=False)
90 90
  91 + _max_latest_threads = 6
  92 +
91 def update_privacy(self): 93 def update_privacy(self):
92 self.is_private = mailman.is_private_list(self.name) 94 self.is_private = mailman.is_private_list(self.name)
93 95
@@ -100,6 +102,20 @@ class MailingList(models.Model): @@ -100,6 +102,20 @@ class MailingList(models.Model):
100 return u'{}?{}'.format(reverse('haystack_search'), 102 return u'{}?{}'.format(reverse('haystack_search'),
101 urllib.urlencode(params)) 103 urllib.urlencode(params))
102 104
  105 + def get_latest(self):
  106 + not_spam_latest = self.thread_set.filter(spam=False)
  107 + ordered_latest = not_spam_latest.order_by(
  108 + '-latest_message__received_time')
  109 + return ordered_latest[:self._max_latest_threads]
  110 +
  111 + def get_most_relevant(self):
  112 + all_most_relevant = Thread.highest_score.filter(
  113 + mailinglist__name=self.name)[:self._max_latest_threads]
  114 + return [thread.latest_message for thread in all_most_relevant]
  115 +
  116 + def get_number_of_users(self):
  117 + return len(mailman.list_users(self.name))
  118 +
103 def __unicode__(self): 119 def __unicode__(self):
104 return self.name 120 return self.name
105 121
colab/super_archives/templates/superarchives/thread-dashboard.html
@@ -7,11 +7,11 @@ @@ -7,11 +7,11 @@
7 <h2>{% trans 'Groups'|title %}</h2> 7 <h2>{% trans 'Groups'|title %}</h2>
8 <hr/> 8 <hr/>
9 9
10 - {% for listname, description, latest, most_relevant, number_of_users in lists %}  
11 - {% if latest or most_relevant %}  
12 - <h3><b>{{ listname|title|lower }} {% if description %} ({{ description }}){% endif %}</b></h3> 10 + {% for mailinglist in lists %}
  11 + {% if mailinglist.get_latest or mailinglist.get_most_relevant %}
  12 + <h3><b>{{ mailinglist.name|title|lower }} {% if mailinglist.description %} ({{ mailinglist.description }}){% endif %}</b></h3>
13 <div class="btn-group btn-group-sm"> 13 <div class="btn-group btn-group-sm">
14 - <a href="#" class="btn btn-default" disabled="disabled">{% blocktrans %}{{ number_of_users }} members{% endblocktrans %}</a> 14 + <a href="#" class="btn btn-default" disabled="disabled">{% blocktrans with number_of_users=mailinglist.get_number_of_users %}{{ number_of_users }} members{% endblocktrans %}</a>
15 </div> 15 </div>
16 <hr/> 16 <hr/>
17 17
@@ -19,12 +19,12 @@ @@ -19,12 +19,12 @@
19 <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12"> 19 <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
20 <h4>{% trans 'latest'|title %}</h4> 20 <h4>{% trans 'latest'|title %}</h4>
21 <ul class="message-list"> 21 <ul class="message-list">
22 - {% for thread in latest %} 22 + {% for thread in mailinglist.get_latest %}
23 {% include "message-preview.html" with result=thread.latest_message %} 23 {% include "message-preview.html" with result=thread.latest_message %}
24 {% endfor %} 24 {% endfor %}
25 </ul> 25 </ul>
26 <div class="text-right"> 26 <div class="text-right">
27 - <a href="{% url 'haystack_search' %}?order=latest&list={{ listname }}&type=thread"> 27 + <a href="{% url 'haystack_search' %}?order=latest&list={{ mailinglist.name }}&type=thread">
28 {% trans "more..." %} 28 {% trans "more..." %}
29 </a> 29 </a>
30 </div> 30 </div>
@@ -33,12 +33,12 @@ @@ -33,12 +33,12 @@
33 <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12"> 33 <div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
34 <h4>{% trans 'most relevant'|title %}</h4> 34 <h4>{% trans 'most relevant'|title %}</h4>
35 <ul class="message-list"> 35 <ul class="message-list">
36 - {% for thread in most_relevant %} 36 + {% for thread in mailinglist.get_most_relevant %}
37 {% include "message-preview.html" with result=thread %} 37 {% include "message-preview.html" with result=thread %}
38 {% endfor %} 38 {% endfor %}
39 </ul> 39 </ul>
40 <div class="text-right"> 40 <div class="text-right">
41 - <a href="{% url 'haystack_search' %}?list={{ listname }}&type=thread"> 41 + <a href="{% url 'haystack_search' %}?list={{ mailinglist.name }}&type=thread">
42 {% trans "more..." %} 42 {% trans "more..." %}
43 </a> 43 </a>
44 </div> 44 </div>
@@ -49,4 +49,52 @@ @@ -49,4 +49,52 @@
49 {% endif %} 49 {% endif %}
50 {% endfor %} 50 {% endfor %}
51 51
  52 + {% if page_obj.has_other_pages %}
  53 + <div class="text-center">
  54 + <ul class="pagination">
  55 + <li {% if page_obj.number == 1 %}class="disabled"{% endif %}>
  56 + <a href="{% if page_obj.number == 1 %}javascript:void(0);{% else %}?page={{1}}{% endif %}"><span class="glyphicon glyphicon-chevron-left small-icon"></span><span class="glyphicon glyphicon-chevron-left small-icon"></span></a>
  57 + </li>
  58 + <li {% if not page_obj.has_previous %}class="disabled"{% endif %}>
  59 + <a href="{% if page_obj.has_previous %}?page={{page_obj.previous_page_number }}{% else %}javascript:void(0);{% endif %}"><span class="glyphicon glyphicon-chevron-left small-icon"></span></a>
  60 + </li>
  61 +
  62 + {% if page_obj.has_previous %}
  63 + {% if page_obj.previous_page_number > 1 %}
  64 + <li>
  65 + <a href="?page={{page_obj.previous_page_number|add:-2 }}">{{ page_obj.number|add:-2 }}</a>
  66 + </li>
  67 + {% endif %}
  68 + <li>
  69 + <a href="?page={{page_obj.previous_page_number }}">{{ page_obj.number|add:-1 }}</a>
  70 + </li>
  71 + {% endif %}
  72 +
  73 + <li class="active">
  74 + <a href="javascript:void(0);">{{ page_obj.number }}</a>
  75 + </li>
  76 +
  77 + {% if page_obj.has_next %}
  78 + <li>
  79 + <a href="?page={{page_obj.next_page_number }}">{{ page_obj.number|add:1 }}</a>
  80 + </li>
  81 + {% if page_obj.next_page_number < page_obj.paginator.num_pages %}
  82 + <li>
  83 + <a href="?page={{page_obj.next_page_number|add:1 }}">{{ page_obj.number|add:2 }}</a>
  84 + </li>
  85 + {% endif %}
  86 + {% endif %}
  87 +
  88 + <li {% if not page_obj.has_next %}class="disabled"{% endif %}>
  89 + <a href="{% if page_obj.has_next %}?page={{page_obj.next_page_number }}{% else %}javascript:void(0);{% endif %}"><span class="glyphicon glyphicon-chevron-right small-icon"></span></a>
  90 + </li>
  91 +
  92 + <li {% if page_obj.number == page_obj.paginator.num_pages %}class="disabled"{% endif %}>
  93 + <a href="{% if page_obj.number == page_obj.paginator.num_pages %}javascript:void(0);{% else %}?page={{page_obj.paginator.num_pages }}{% endif %}"><span class="glyphicon glyphicon-chevron-right small-icon"></span><span class="glyphicon glyphicon-chevron-right small-icon"></span></a>
  94 + </li>
  95 +
  96 + </ul>
  97 + </div>
  98 + {% endif %}
  99 +
52 {% endblock %} 100 {% endblock %}
colab/super_archives/tests/test_privatelist.py
@@ -17,18 +17,18 @@ class ArchivesViewTest(TestCase): @@ -17,18 +17,18 @@ class ArchivesViewTest(TestCase):
17 17
18 def test_see_only_private_list_if_member(self): 18 def test_see_only_private_list_if_member(self):
19 mailman.get_user_mailinglists = mock.Mock( 19 mailman.get_user_mailinglists = mock.Mock(
20 - return_value="[{'listname': 'privatelist'}]") 20 + return_value=[{'listname': 'privatelist'}])
21 mailman.extract_listname_from_list = mock.Mock( 21 mailman.extract_listname_from_list = mock.Mock(
22 - return_value="['privatelist']")  
23 - mailman.list_users = mock.Mock(return_value="['johndoe@example.com']") 22 + return_value=['privatelist'])
  23 + mailman.list_users = mock.Mock(return_value=['johndoe@example.com'])
24 24
25 self.authenticate_user() 25 self.authenticate_user()
26 request = self.client.get('/archives/thread/') 26 request = self.client.get('/archives/thread/')
27 27
28 list_data = request.context['lists'] 28 list_data = request.context['lists']
29 29
30 - self.assertEqual('lista', list_data[0][0])  
31 - self.assertEqual('privatelist', list_data[1][0]) 30 + self.assertEqual('lista', list_data[0].name)
  31 + self.assertEqual('privatelist', list_data[1].name)
32 self.assertEqual(2, len(list_data)) 32 self.assertEqual(2, len(list_data))
33 33
34 def test_see_only_public_if_not_logged_in(self): 34 def test_see_only_public_if_not_logged_in(self):
@@ -36,7 +36,7 @@ class ArchivesViewTest(TestCase): @@ -36,7 +36,7 @@ class ArchivesViewTest(TestCase):
36 36
37 list_data = request.context['lists'] 37 list_data = request.context['lists']
38 38
39 - self.assertEqual('lista', list_data[0][0]) 39 + self.assertEqual('lista', list_data[0].name)
40 self.assertEqual(1, len(list_data)) 40 self.assertEqual(1, len(list_data))
41 41
42 def test_see_private_thread_in_dashboard_if_member(self): 42 def test_see_private_thread_in_dashboard_if_member(self):
colab/super_archives/views.py
@@ -11,7 +11,8 @@ from django.conf import settings @@ -11,7 +11,8 @@ from django.conf import settings
11 from django.contrib import messages 11 from django.contrib import messages
12 from django.core.urlresolvers import reverse 12 from django.core.urlresolvers import reverse
13 from django.db import IntegrityError 13 from django.db import IntegrityError
14 -from django.views.generic import View 14 +from django.db.models import Q
  15 +from django.views.generic import View, ListView
15 from django.utils.translation import ugettext as _ 16 from django.utils.translation import ugettext as _
16 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied 17 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
17 from django.utils.decorators import method_decorator 18 from django.utils.decorators import method_decorator
@@ -137,42 +138,23 @@ class ThreadView(View): @@ -137,42 +138,23 @@ class ThreadView(View):
137 return self.get(request, mailinglist, thread_token) 138 return self.get(request, mailinglist, thread_token)
138 139
139 140
140 -class ThreadDashboardView(View): 141 +class ThreadDashboardView(ListView):
141 http_method_names = ['get'] 142 http_method_names = ['get']
  143 + context_object_name = 'lists'
  144 + template_name = 'superarchives/thread-dashboard.html'
  145 + paginate_by = 10
142 146
143 - def get(self, request):  
144 - MAX = 6  
145 - context = {}  
146 -  
147 - all_privates = {}  
148 - private_mailinglist = MailingList.objects.filter(is_private=True)  
149 - for mailinglist in private_mailinglist:  
150 - all_privates[mailinglist.name] = True  
151 -  
152 - context['lists'] = []  
153 - 147 + def get_queryset(self):
154 listnames_for_user = [] 148 listnames_for_user = []
155 - if request.user.is_authenticated():  
156 - user = User.objects.get(username=request.user) 149 + if self.request.user.is_authenticated():
  150 + user = User.objects.get(username=self.request.user)
157 lists_for_user = mailman.get_user_mailinglists(user) 151 lists_for_user = mailman.get_user_mailinglists(user)
158 listnames_for_user = mailman.extract_listname_from_list( 152 listnames_for_user = mailman.extract_listname_from_list(
159 lists_for_user) 153 lists_for_user)
160 154
161 - for list_ in MailingList.objects.order_by('name'):  
162 - if list_.name not in all_privates\  
163 - or list_.name in listnames_for_user:  
164 - context['lists'].append((  
165 - list_.name,  
166 - mailman.get_list_description(list_.name),  
167 - list_.thread_set.filter(spam=False).order_by(  
168 - '-latest_message__received_time'  
169 - )[:MAX],  
170 - [t.latest_message for t in Thread.highest_score.filter(  
171 - mailinglist__name=list_.name)[:MAX]],  
172 - len(mailman.list_users(list_.name)),  
173 - ))  
174 -  
175 - return render(request, 'superarchives/thread-dashboard.html', context) 155 + query = Q(is_private=False) | Q(name__in=listnames_for_user)
  156 +
  157 + return MailingList.objects.filter(query).order_by('name')
176 158
177 159
178 class EmailView(View): 160 class EmailView(View):