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