Merge lp:~andrewsomething/dat-overview/new_contacted_logic into lp:dat-overview

Proposed by Andrew Starr-Bochicchio on 2013-06-23
Status: Merged
Merged at revision: 32
Proposed branch: lp:~andrewsomething/dat-overview/new_contacted_logic
Merge into: lp:dat-overview
Diff against target: 633 lines (+242/-109)
12 files modified
overview/local_settings.py.sample (+4/-2)
overview/media/css/base.css (+27/-0)
overview/settings.py (+6/-0)
overview/templates/first_timers.html (+13/-8)
overview/templates/lost_contributors.html (+3/-1)
overview/templates/person.html (+41/-9)
overview/templates/potential_devs.html (+5/-1)
overview/uploads/forms.py (+0/-3)
overview/uploads/models.py (+3/-1)
overview/uploads/templatetags/custom_tags.py (+5/-0)
overview/uploads/views.py (+133/-83)
overview/urls.py (+2/-1)
To merge this branch: bzr merge lp:~andrewsomething/dat-overview/new_contacted_logic
Reviewer Review Type Date Requested Status
Daniel Holbach 2013-06-23 Approve on 2013-06-24
Review via email: mp+170988@code.launchpad.net

Description of the change

This branch adds the comment-style system for tracking contacts. It also
provides some optimization of the queries used to generate the views making
things a bit snappier.

To post a comment you must log in.
Bhavani Shankar (bhavi) wrote :

Works great here! Awesome stuff again!

Thanks Andrew!

Regards
Bhavani

Daniel Holbach (dholbach) wrote :

That's excellent work! :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'overview/local_settings.py.sample'
2--- overview/local_settings.py.sample 2013-04-06 18:45:17 +0000
3+++ overview/local_settings.py.sample 2013-06-23 19:42:22 +0000
4@@ -18,5 +18,7 @@
5 }
6 }
7
8-
9-
10+# Uncomment to use django debug_toolbar. Requires: python-django-ebug-toolbar
11+#DEBUG_MIDDLEWARE_CLASSES = ('debug_toolbar.middleware.DebugToolbarMiddleware',)
12+#DEBUG_APPS = ('debug_toolbar',)
13+#INTERNAL_IPS = ('127.0.0.1',)
14
15=== modified file 'overview/media/css/base.css'
16--- overview/media/css/base.css 2013-06-18 23:57:17 +0000
17+++ overview/media/css/base.css 2013-06-23 19:42:22 +0000
18@@ -364,6 +364,29 @@
19 box-shadow: 1px 1px 5px #CCC;
20 }
21
22+#comment{
23+ max-width: 600px;
24+ border-radius: 3px;
25+ border: 1px solid #CCC;
26+ padding: 8px;
27+ font-weight: 200;
28+ font-size: 15px;
29+ font-family: Ubuntu;
30+ box-shadow: 1px 1px 5px #CCC;
31+ position: relative;
32+}
33+
34+#comment-details{
35+ padding-left:275px;
36+}
37+
38+#comment h3{
39+ position: absolute;
40+ bottom: 0;
41+ right: 5px;
42+ color:#A9A9A9;
43+}
44+
45 .legend{
46 float:right;
47 }
48@@ -380,3 +403,7 @@
49 min-height: 304px;
50 background-repeat: no-repeat, repeat;
51 }
52+
53+#id_honeypot {
54+ display: none;
55+}
56
57=== modified file 'overview/settings.py'
58--- overview/settings.py 2013-06-10 16:42:05 +0000
59+++ overview/settings.py 2013-06-23 19:42:22 +0000
60@@ -121,6 +121,7 @@
61 'django.contrib.staticfiles',
62 'django_openid_auth',
63 'django.contrib.admin',
64+ 'django.contrib.comments',
65 # Uncomment the next line to enable admin documentation:
66 # 'django.contrib.admindocs',
67 'uploads',
68@@ -187,7 +188,12 @@
69 }
70 }
71
72+
73+DEBUG_APPS = ()
74+DEBUG_MIDDLEWARE_CLASSES = ()
75 try:
76 from local_settings import *
77+ INSTALLED_APPS += DEBUG_APPS
78+ MIDDLEWARE_CLASSES += DEBUG_MIDDLEWARE_CLASSES
79 except ImportError:
80 logging.warning("No local_settings.py were found. See INSTALL for instructions.")
81
82=== modified file 'overview/templates/first_timers.html'
83--- overview/templates/first_timers.html 2013-06-19 20:56:21 +0000
84+++ overview/templates/first_timers.html 2013-06-23 19:42:22 +0000
85@@ -1,4 +1,5 @@
86 {% extends "base.html" %}
87+{% load custom_tags %}
88
89 {% block content %}
90
91@@ -29,8 +30,9 @@
92 {% for i in output|dictsortreversed:"p.first_upload.timestamp" %}
93 {% if 'saucy' in i.p.first_upload.release %}
94 <tr class='{{ i.color }}'>
95- <td>{% if i.p.contacted %}
96- <center>✓</center>{% endif %}
97+ <td>{% if i.p|recent_contact %}
98+ <center>✓</center>
99+ {% endif %}
100 </td>
101 <td><a href="{{ i.p.lpid }}">{{ i.p.name }}</a></td>
102 <td>
103@@ -68,8 +70,9 @@
104 {% for i in output|dictsortreversed:"p.first_upload.timestamp" %}
105 {% if 'raring' in i.p.first_upload.release %}
106 <tr class='{{ i.color }}'>
107- <td>{% if i.p.contacted %}
108- <center>✓</center>{% endif %}
109+ <td>{% if i.p|recent_contact %}
110+ <center>✓</center>
111+ {% endif %}
112 </td>
113 <td><a href="{{ i.p.lpid }}">{{ i.p.name }}</a></td>
114 <td>
115@@ -107,8 +110,9 @@
116 {% for i in output|dictsortreversed:"p.first_upload.timestamp" %}
117 {% if 'quantal' in i.p.first_upload.release %}
118 <tr class='{{ i.color }}'>
119- <td>{% if i.p.contacted %}
120- <center>✓</center>{% endif %}
121+ <td>{% if i.p|recent_contact %}
122+ <center>✓</center>
123+ {% endif %}
124 </td>
125 <td><a href="{{ i.p.lpid }}">{{ i.p.name }}</a></td>
126 <td>
127@@ -146,8 +150,9 @@
128 {% for i in output|dictsortreversed:"p.first_upload.timestamp" %}
129 {% if 'precise' in i.p.first_upload.release %}
130 <tr class='{{ i.color }}'>
131- <td>{% if i.p.contacted %}
132- <center>✓</center>{% endif %}
133+ <td>{% if i.p|recent_contact %}
134+ <center>✓</center>
135+ {% endif %}
136 </td>
137 <td><a href="{{ i.p.lpid }}">{{ i.p.name }}</a></td>
138 <td>
139
140=== modified file 'overview/templates/lost_contributors.html'
141--- overview/templates/lost_contributors.html 2013-06-19 20:29:36 +0000
142+++ overview/templates/lost_contributors.html 2013-06-23 19:42:22 +0000
143@@ -18,7 +18,9 @@
144 </tr>
145 {% for p in lost_contributors|dictsortreversed:"last_upload.timestamp" %}
146 <tr>
147- <td>{% if p.contacted %}<center>✓</center>{% endif %}</td>
148+ <td>{% if p|recent_contact > p.last_upload.timestamp %}
149+ <center>✓</center>{% endif %}
150+ </td>
151 <td>
152 <a href="{{ p.lpid }}">{{ p.name }}</a>{% ubu_dev_img p.ubuntu_dev %}
153 </td>
154
155=== modified file 'overview/templates/person.html'
156--- overview/templates/person.html 2013-06-21 20:24:21 +0000
157+++ overview/templates/person.html 2013-06-23 19:42:22 +0000
158@@ -1,5 +1,6 @@
159 {% extends "base.html" %}
160 {% load custom_tags %}
161+{% load comments %}
162
163 {% block extrahead %}
164
165@@ -46,24 +47,18 @@
166 <li><b>Ubuntu Developer:</b> {{ person.ubuntu_dev|yesno:"Yes,No" }}</li>
167 <li><b>Active:</b> {{ person.is_active|yesno:"Yes,No" }}</li>
168 <li><b>Last Seen:</b> {{ person.last_upload.timestamp }}</li>
169+ <li><b>Last Contact:</b> {{ person|recent_contact }}</li>
170 <li><b>Total uploads:</b> {{ person.total_uploads }}</li>
171 {% if ppu_candidates %}
172 <li><b>PPU Candidates:</b>
173 {% for pkg in ppu_candidates %}
174- <a href="https://launchpad.net/ubuntu/+source/{{ pkg }}">{{ pkg }}</a>,</li>
175+ <a href="https://launchpad.net/ubuntu/+source/{{ pkg }}">{{ pkg }}</a>, </li>
176 {% endfor %}
177 {% endif %}
178+
179 </ul>
180 </div>
181
182-<div id='contacted_form'>
183- <h3>Contacted?</h3>
184- <form action="#" method="post">{% csrf_token %}
185- {{ contacted.checkbox }}
186- <input type="submit" name='update_contacted' value="Save" />
187- </form>
188-</div>
189-
190 <div id='whiteboard'>
191 <h2>Whiteboard:</h2>
192
193@@ -73,6 +68,43 @@
194 </form>
195 </div>
196
197+<div id='contacts'>
198+
199+ <h2>Record contact:</h2>
200+
201+ <div id='comment-form'>
202+ {% get_comment_form for person as form %}
203+ <form action="{% comment_form_target %}" method="post">
204+ {% csrf_token %}
205+ {{ form.comment }}
206+ {{ form.honeypot }}
207+ {{ form.content_type }}
208+ {{ form.object_pk }}
209+ {{ form.timestamp }}
210+ {{ form.security_hash }}
211+ <input type="hidden" name="next" value="/contributors/{{ person.lpid }}#" />
212+ <input type="submit" name="submit" value="Record contact">
213+ </form>
214+ </div>
215+
216+ <h2>Contacts:</h2>
217+
218+ {% get_comment_list for person as comment_list %}
219+ {% for comment in comment_list reversed %}
220+
221+ <div id='comment'>
222+ {{ comment.comment|linebreaks }}
223+ <h3>#{{ forloop.revcounter }}</h3>
224+ </div>
225+
226+ <div id='comment-details'>
227+ by <a href="/users/{{ comment.user }}">{{ comment.user }}</a> on {{ comment.submit_date }}
228+ </div>
229+ <br>
230+ {% endfor %}
231+
232+</div>
233+
234 <div id='uploads_per_release'>
235 <h2>Uploads per release:</h2>
236 <div id="uploads_per_release_chart"></div>
237
238=== modified file 'overview/templates/potential_devs.html'
239--- overview/templates/potential_devs.html 2013-06-19 20:29:36 +0000
240+++ overview/templates/potential_devs.html 2013-06-23 19:42:22 +0000
241@@ -1,4 +1,5 @@
242 {% extends "base.html" %}
243+{% load custom_tags %}
244
245 {% block content %}
246
247@@ -15,9 +16,12 @@
248 <th>Uploads</th>
249 <th>Dates Active</th>
250 </tr>
251+
252 {% for p in potential_devs|dictsortreversed:"first_upload.timestamp" %}
253 <tr>
254- <td>{% if p.contacted %}<center>✓</center>{% endif %}</td>
255+ <td>{% if p|recent_contact > p.fortieth %}
256+ <center>✓</center>{% endif %}
257+ </td>
258 <td><a href="{{ p.lpid }}">{{ p.name }}</a></td>
259 <td>{{ p.total_uploads }}</td>
260 <td>{{ p.first_upload.timestamp|date:"SHORT_DATE_FORMAT" }} to {{ p.last_upload.timestamp|date:"SHORT_DATE_FORMAT" }}</td>
261
262=== modified file 'overview/uploads/forms.py'
263--- overview/uploads/forms.py 2013-04-23 18:32:37 +0000
264+++ overview/uploads/forms.py 2013-06-23 19:42:22 +0000
265@@ -3,9 +3,6 @@
266 class NotesForm(forms.Form):
267 notes = forms.CharField(widget = forms.widgets.Textarea(), required=False)
268
269-class BooleanForm(forms.Form):
270- checkbox = forms.BooleanField(required=False)
271-
272 class EditContrib(forms.Form):
273 lpid = forms.CharField(label='Launchpad ID: ', required=True)
274 email = forms.EmailField(label='Email: ', required=True)
275
276=== modified file 'overview/uploads/models.py'
277--- overview/uploads/models.py 2013-06-10 16:42:05 +0000
278+++ overview/uploads/models.py 2013-06-23 19:42:22 +0000
279@@ -1,5 +1,7 @@
280 from django.db import models
281 from django.contrib.auth.models import User
282+from django.contrib.contenttypes import generic
283+from django.contrib.comments.models import Comment
284
285 class UDD(models.Model):
286 connection_name='udd'
287@@ -41,7 +43,7 @@
288 last_upload = models.ForeignKey('Uploads', related_name='+')
289 ubuntu_dev = models.BooleanField(default=False)
290 notes = models.TextField(blank=True)
291- contacted = models.BooleanField(default=False)
292+ contacts = generic.GenericRelation(Comment, object_id_field="object_pk")
293
294 class Meta:
295 db_table = u'people'
296
297=== modified file 'overview/uploads/templatetags/custom_tags.py'
298--- overview/uploads/templatetags/custom_tags.py 2013-04-06 22:04:28 +0000
299+++ overview/uploads/templatetags/custom_tags.py 2013-06-23 19:42:22 +0000
300@@ -5,3 +5,8 @@
301 @register.inclusion_tag('ubu_dev_flag_img.html')
302 def ubu_dev_img(flag):
303 return {'flag': flag}
304+
305+@register.filter
306+def recent_contact(person):
307+ if person.contacts.all():
308+ return person.contacts.all().reverse()[0].submit_date
309
310=== modified file 'overview/uploads/views.py'
311--- overview/uploads/views.py 2013-06-21 20:24:21 +0000
312+++ overview/uploads/views.py 2013-06-23 19:42:22 +0000
313@@ -13,13 +13,17 @@
314 from uploads.decorators import group_perm_required
315 from uploads.models import Uploads, People, UserProfile
316 from django.contrib.auth.models import User
317-from uploads.forms import NotesForm, BooleanForm, EditContrib
318+from uploads.forms import NotesForm, EditContrib
319+
320+from django.dispatch import receiver
321+from django.contrib.comments.signals import comment_was_posted
322
323
324 @group_perm_required()
325 def first_timers(request):
326 output = []
327- for p in People.objects.all():
328+ for p in People.objects.all().prefetch_related("first_upload"
329+ ).prefetch_related("last_upload"):
330 if p.total_uploads < 5:
331 color = "lt5"
332 if p.total_uploads >= 5 and p.total_uploads < 10:
333@@ -41,21 +45,8 @@
334 person = get_object_or_404(People, lpid=lpid)
335 uploads = Uploads.objects.filter(lpid_changer=lpid)
336 recent_uploads = uploads.order_by('timestamp').reverse()[0:10]
337- uploads_per_release = OrderedDict([])
338- for d in UbuntuDistroInfo().all:
339- release_uploads = len(Uploads.objects.filter(lpid_changer=lpid).filter(release__icontains=d))
340- if uploads_per_release or release_uploads > 0:
341- uploads_per_release[d] = release_uploads
342- packages = []
343- ppu_candidates = []
344- for ul in uploads:
345- packages += [ul.package]
346- appearances = defaultdict(int)
347- for curr in packages:
348- appearances[curr] += 1
349- for pkg in appearances:
350- if appearances[pkg] > 5:
351- ppu_candidates += [pkg]
352+ uploads_per_release = get_uploads_per_release(lpid)
353+ ppu_candidates = get_ppu_candidates(uploads)
354 if request.method == 'POST':
355 if 'save_notes' in request.POST:
356 notes_form = NotesForm(request.POST)
357@@ -67,56 +58,75 @@
358 log_action(person, change_message, request.user.pk)
359 messages.success(request, 'Change successfully saved...')
360 return HttpResponseRedirect('#')
361- elif 'update_contacted' in request.POST:
362- contacted = BooleanForm(request.POST)
363- notes_form = NotesForm(initial={'notes': person.notes})
364- if contacted.is_valid():
365- person.contacted = contacted.cleaned_data['checkbox']
366- person.save()
367- change_message = "Marked %s as contacted." % person.name
368- log_action(person, change_message, request.user.pk)
369- messages.success(request, 'Change successfully saved...')
370- return HttpResponseRedirect('#')
371 else:
372 notes_form = NotesForm(initial={'notes': person.notes})
373- contacted = BooleanForm(initial={'checkbox': person.contacted})
374 return render(request, 'person.html', {'person': person,
375 'recent_uploads': recent_uploads,
376 'ppu_candidates': ppu_candidates,
377 'notes_form': notes_form,
378- 'contacted': contacted,
379- 'uploads_per_release': uploads_per_release})
380+ 'uploads_per_release':
381+ uploads_per_release})
382+
383+
384+def get_ppu_candidates(uploads):
385+ """
386+ Takes an Uploads object filtered by lpid_changer and returns
387+ a list of package that were uploaded by a contributor more than
388+ five times.
389+ """
390+ packages = uploads.values_list('package', flat=True)
391+ ppu_candidates = []
392+ appearances = defaultdict(int)
393+ for curr in packages:
394+ appearances[curr] += 1
395+ for pkg in appearances:
396+ if appearances[pkg] > 5:
397+ ppu_candidates += [pkg]
398+ return ppu_candidates
399+
400+
401+def get_uploads_per_release(lpid):
402+ """
403+ Takes an lpid and returns an ordered dict of uploads per release.
404+ """
405+ uploads_per_release = OrderedDict([])
406+ for d in UbuntuDistroInfo().all:
407+ release_uploads = len(Uploads.objects.filter(
408+ lpid_changer=lpid).filter(release__icontains=d))
409+ if uploads_per_release or release_uploads > 0:
410+ uploads_per_release[d] = release_uploads
411+ return uploads_per_release
412
413
414 @group_perm_required()
415 def edit_person(request, lpid):
416 person = get_object_or_404(People, lpid=lpid)
417 if request.method == 'POST':
418- if request.method == 'POST':
419- person_form = EditContrib(request.POST)
420- if person_form.is_valid():
421- new_lpid = person_form.cleaned_data['lpid']
422- person.email = person_form.cleaned_data['email']
423- person.lpid = new_lpid
424- person.save()
425- if lpid is not new_lpid:
426- uploads = Uploads.objects.filter(lpid_changer=lpid)
427- uploads.update(lpid_changer = new_lpid)
428- change_message = "Updated %s's details." % person.name
429- log_action(person, change_message, request.user.pk)
430- messages.success(request, 'Change successfully saved...')
431- return HttpResponseRedirect('/contributors/{}'.format(new_lpid))
432+ person_form = EditContrib(request.POST)
433+ if person_form.is_valid():
434+ new_lpid = person_form.cleaned_data['lpid']
435+ person.email = person_form.cleaned_data['email']
436+ person.lpid = new_lpid
437+ person.save()
438+ if lpid is not new_lpid:
439+ uploads = Uploads.objects.filter(lpid_changer=lpid)
440+ uploads.update(lpid_changer=new_lpid)
441+ change_message = "Updated %s's details." % person.name
442+ log_action(person, change_message, request.user.pk)
443+ messages.success(request, 'Change successfully saved...')
444+ return HttpResponseRedirect('/contributors/{}'.format(new_lpid))
445 else:
446 person_form = EditContrib(initial={'lpid': lpid,
447 'email': person.email})
448 return render(request, 'edit_person.html', {'person': person,
449 'person_form': person_form})
450
451+
452 @group_perm_required()
453 def recent_contributors(request):
454 recent_contributors = []
455- for p in People.objects.all():
456- cutoff_date = timezone.now() - timedelta(days=2 * 30)
457+ cutoff_date = timezone.now() - timedelta(days=2 * 30)
458+ for p in People.objects.all().prefetch_related("last_upload"):
459 if p.last_upload.timestamp > cutoff_date:
460 recent_contributors += [p]
461 return render(request, 'recent_contributors.html',
462@@ -126,9 +136,9 @@
463 @group_perm_required()
464 def lost_contributors(request):
465 lost_contributors = []
466- for p in People.objects.all():
467- one_year = timezone.now() - timedelta(days=365)
468- two_months = timezone.now() - timedelta(days=2 * 30)
469+ one_year = timezone.now() - timedelta(days=365)
470+ two_months = timezone.now() - timedelta(days=2 * 30)
471+ for p in People.objects.all().prefetch_related('last_upload'):
472 ul_date = p.last_upload.timestamp
473 if ul_date > one_year and ul_date < two_months:
474 lost_contributors += [p]
475@@ -139,48 +149,63 @@
476 @group_perm_required()
477 def potential_devs(request):
478 potential_devs = []
479- for p in People.objects.all():
480- six_months = timezone.now() - timedelta(days=6 * 30)
481- if ((p.first_upload.timestamp < six_months
482- and p.ubuntu_dev is not True
483- and p.total_uploads >= 40
484- and p.is_active is True)):
485- potential_devs += [p]
486+ cutoff_upload = {}
487+ six_months = timezone.now() - timedelta(days=6 * 30)
488+ for p in People.objects.filter(ubuntu_dev=False
489+ ).filter(total_uploads__gte=40
490+ ).filter(is_active=True
491+ ).prefetch_related("first_upload"
492+ ).prefetch_related("last_upload"
493+ ).filter(first_upload__timestamp__lte=six_months):
494+ uploads = Uploads.objects.filter(lpid_changer=p.lpid)
495+ p.fortieth = uploads.order_by("timestamp")[39].timestamp
496+ potential_devs += [p]
497 return render(request, 'potential_devs.html',
498- {'potential_devs': potential_devs})
499+ {'potential_devs': potential_devs,
500+ 'cutoff_upload': cutoff_upload})
501+
502
503 def log_action(object, change_message, user):
504 LogEntry.objects.log_action(
505- user_id = user,
506- content_type_id = ContentType.objects.get_for_model(object).pk,
507- object_id = object.pk,
508- object_repr = object.lpid,
509- change_message = change_message,
510- action_flag = ADDITION
511+ user_id=user,
512+ content_type_id=ContentType.objects.get_for_model(object).pk,
513+ object_id=object.pk,
514+ object_repr=object.lpid,
515+ change_message=change_message,
516+ action_flag=ADDITION
517 )
518
519+
520 @group_perm_required()
521 def user_profile(request, user):
522 profile = User.objects.get(username=user).profile
523 actions = LogEntry.objects.filter(user_id=profile.user_id)
524- edited_contribs = actions.order_by('object_repr').values_list("object_repr",
525- flat=True).distinct()
526- return render(request, 'user_profile.html',{'profile': profile,
527- 'actions': actions,
528- 'edited_contribs': edited_contribs})
529+ edited_contribs = actions.order_by('object_repr'
530+ ).values_list("object_repr",
531+ flat=True).distinct()
532+ return render(request, 'user_profile.html', {'profile': profile,
533+ 'actions': actions,
534+ 'edited_contribs':
535+ edited_contribs})
536+
537
538 def site_logout(request):
539 logout(request)
540 messages.success(request, 'Logged out successfully...')
541 return HttpResponseRedirect('/')
542
543+
544 def access_denied(request, redirect):
545- messages.error(request, 'You do not have the correct permissions to view that page...')
546+ messages.error(request, """
547+ You do not have the correct permissions to view that page...
548+ """)
549 return HttpResponseRedirect(redirect)
550
551+
552 def index(request):
553 return render(request, 'index.html', dashboard())
554
555+
556 def dashboard():
557 first_timers = []
558 experienced = []
559@@ -189,21 +214,46 @@
560 two_months = timezone.now() - timedelta(days=2 * 30)
561 six_months = timezone.now() - timedelta(days=6 * 30)
562 one_year = timezone.now() - timedelta(days=365)
563- for p in People.objects.all().order_by('last_upload__timestamp').reverse():
564- if (len(first_timers) < 20 and p.first_upload.timestamp > three_months
565- and p.contacted is False):
566+ people = People.objects.all().prefetch_related("first_upload"
567+ ).prefetch_related("last_upload"
568+ ).select_related('contacts'
569+ ).order_by('last_upload__timestamp').reverse()
570+ first_timers_qs = people.filter(first_upload__timestamp__gte=three_months)
571+ experienced_qs = people.filter(ubuntu_dev=False
572+ ).filter(total_uploads__gte=40
573+ ).filter(is_active=True)
574+ inactive_qs = people.filter(last_upload__timestamp__gt=one_year
575+ ).filter(last_upload__timestamp__lt=two_months
576+ ).filter(total_uploads__gte=5)
577+ for p in first_timers_qs:
578+ if (len(first_timers) < 20 and not p.contacts.all()):
579 first_timers.append(p)
580- if (len(experienced) < 20 and p.first_upload.timestamp < six_months
581- and p.ubuntu_dev is not True
582- and p.total_uploads >= 40
583- and p.is_active is True
584- and p.contacted is False):
585- experienced.append(p)
586- if len(inactive) < 20:
587- ul_date = p.last_upload.timestamp
588- if (ul_date > one_year and ul_date < two_months
589- and p.contacted is False and p.total_uploads >= 5):
590- inactive.append(p)
591+ for p in experienced_qs:
592+ if p.contacts.all():
593+ recent_c = p.contacts.all().reverse()[0].submit_date
594+ else:
595+ recent_c = None
596+ if (len(experienced) < 20 and (recent_c is None or
597+ recent_c < Uploads.objects.filter(lpid_changer=p.lpid
598+ ).order_by("timestamp"
599+ )[39].timestamp)):
600+ experienced.append(p)
601+ for p in inactive_qs:
602+ if p.contacts.all():
603+ recent_c = p.contacts.all().reverse()[0].submit_date
604+ else:
605+ recent_c = None
606+ if len(inactive) < 20 and (recent_c is None or
607+ recent_c < p.last_upload.timestamp):
608+ inactive.append(p)
609 return {'first_timers': first_timers,
610 'experienced': experienced,
611 'inactive': inactive}
612+
613+
614+@receiver(comment_was_posted)
615+def on_contact_saved(sender, comment=None, request=None, **kwargs):
616+ person = People.objects.get(pk=comment.object_pk)
617+ change_message = "Recorded a contact with %s." % person.name
618+ log_action(person, change_message, comment.user.pk)
619+ messages.success(request, 'Change successfully saved...')
620
621=== modified file 'overview/urls.py'
622--- overview/urls.py 2013-06-12 18:37:58 +0000
623+++ overview/urls.py 2013-06-23 19:42:22 +0000
624@@ -29,7 +29,8 @@
625 name='potential_devs'),
626 url(r'^contributors/(?P<lpid>.+)/edit', views.edit_person, name='edit_person'),
627 url(r'^contributors/(?P<lpid>.+)', views.person_detail, name='person_detail'),
628- url(r'^users/(?P<user>.+)', views.user_profile, name='user_profile')
629+ url(r'^users/(?P<user>.+)', views.user_profile, name='user_profile'),
630+ url(r'^comments/', include('django.contrib.comments.urls'))
631 )
632
633 if settings.STATIC_SERVE:

Subscribers

People subscribed via source and target branches