Merge lp:~cjohnston/summit/add-user-profiles into lp:summit

Proposed by Chris Johnston
Status: Work in progress
Proposed branch: lp:~cjohnston/summit/add-user-profiles
Merge into: lp:summit
Diff against target: 604 lines (+489/-2)
15 files modified
summit/common/forms.py (+40/-0)
summit/common/launchpad.py (+27/-0)
summit/schedule/models/summitmodel.py (+1/-1)
summit/schedule/tests.py (+3/-1)
summit/settings.py (+1/-0)
summit/urls.py (+5/-0)
summit/userprofiles/admin.py (+7/-0)
summit/userprofiles/forms.py (+25/-0)
summit/userprofiles/management/commands/update-profiles.py (+39/-0)
summit/userprofiles/migrations/0001_add_user_profiles.py (+93/-0)
summit/userprofiles/models.py (+82/-0)
summit/userprofiles/templates/userprofiles/profile.html (+43/-0)
summit/userprofiles/templates/userprofiles/profile_update.html (+41/-0)
summit/userprofiles/tests.py (+16/-0)
summit/userprofiles/views.py (+66/-0)
To merge this branch: bzr merge lp:~cjohnston/summit/add-user-profiles
Reviewer Review Type Date Requested Status
Summit Hackers Pending
Review via email: mp+85194@code.launchpad.net
To post a comment you must log in.
233. By Chris Johnston

[r=nigelbabu] Adds a sponsorship reviews state to summit so that links only show up when needed.

234. By Chris Johnston

[r=nigelbabu] Removes videographer and adds video fields.

235. By Chris Johnston

[r=nigelbabu] Adds track page with info about tracks.

236. By Chris Johnston

[r=chrisjohnston] Fixes token problems

237. By Данило Шеган

[r=chrisjohnston] Changes meeting name to 100 chars

238. By Chris Johnston

[r=nigelbabu,danilo] Adds manager and scheduler user roles to summit

239. By Michael Hall

[r=chrisjohnston] Adds private meeting urls for sharing access via emailed links

240. By Chris Johnston

[r=mhall119] Adds the ability for admins to add participants to a meeting

241. By Michael Hall

[r=nigelbabu] Sort attendees by user.username first, and summit second, to allow easier searching

242. By Chris Johnston

Fixes plenaries showing up on wrong days

243. By Michael Hall

[r=chrisjohnston] Removes obsolete tests and fix others

244. By Michael Hall

[r=chrisjohnston] Adds links to quickly add or remove yourself as a participant in a meeting.

245. By Chris Johnston

[r=mhall119] Adds ability for track leads, managers and schedulers to create private meetings from the UI

246. By Michael Hall

[r=chrisjohnston] Adds a new display for daily schedules that is more convenient for viewing on a desktop

247. By Michael Hall

[r=chrisjohnston] Add icecast urls to the new schedule

248. By Chris Johnston

[r=mhall119] This removes the images from the meeting fields on the widescreen page as well as changing the text to direct to the meeting page.

249. By Michael Hall

[r=chrisjohnston] Removed Linaro hacks

250. By Michael Hall

[r=chrisjohnston] Hide plenary room from room list unless the user can edit the schedule

251. By Chris Johnston

[r=mhall119] Adds can change agenda to create and edit PM

252. By Michael Hall

[r=chrisjohnston] Display time headers in 24-hour time, add end time

253. By Michael Hall

[r=chrisjohnston] Add management command to import summit schedule data.

254. By Chris Johnston

[r=james-w] Moves templates to common app

255. By Chris Johnston

[r=james-w] Remove remaining i18n stuff

256. By Chris Johnston

[r=james-w] Fixes spelling issue

257. By Michael Hall

[r=chrisjohnston] Fix localtime conversion issues

258. By Michael Hall

[r=chrisjohnston] Fix wide schedule links and tests

259. By Chris Johnston

[r=mhall119] Renames the app to "The Summit Scheduler" and adds/fixes page titles across the board.

260. By Chris Johnston

[r=mhall119] This removes the rest of the hacks for having multiple plenaries at the same time which was breaking the widescreen and daily displays.

261. By Chris Johnston

Starts adding social media to summit by adding opengraph

262. By Данило Шеган

Do not crash when multiple agendas are returned for the same time slot (eg. due to multiple plenary rooms)

263. By Chris Johnston

Add lunch and plenary labels to the new schedule

264. By Chris Johnston

Adds twitter hash tag to the summit model, displays twidenash on the main summit page

265. By Michael Hall

force available times on test rooms so that they will be on the schedule when they should be

266. By Chris Johnston

Version 1.0.0 Release

267. By Chris Johnston

Adds fixes from mhall119

268. By Chris Johnston

Adds url fix from danilos

269. By Chris Johnston

Release version 1.0.1

270. By Chris Johnston

[r=danilo] Removes "Private:" from the schedule list if there are no private rooms

271. By Данило Шеган

[r=chrisjohnston] Makes it possible for the creator of a private meeting to add required participants.

272. By Chris Johnston

[r=danilo] Removes "Lead:" if there is no track lead set

273. By Chris Johnston

[r=danilo] Removes empty subnav from meeting pages

274. By Chris Johnston

[r=chrisjohnston,danilo] Removes the requirement to set participants for a private meeting, adds help text.

275. By Chris Johnston

Version 1.0.2 release

276. By Michael Hall

[r=chrisjohnston] A quick and dirty monkey patching to give Users a sane order, added users real name in addition to their launchpad id, on meeting page also added real name in addition to launchpad id for participants.

277. By Michael Hall

[r=chrisjohnston] Fixes reverse lookup errors on meetings with no name and past summits page

278. By Chris Johnston

[r=mhall119] Makes the leads display on the track display smaller

279. By Chris Johnston

Allows etherpad url to be defined in the summit model

280. By Chris Johnston

Version 1.0.3 release

281. By Stuart Langridge

[r=mhall119] Custom styles for devices less than 480px wide; tiny bit of JS to override the hover stuff and toggle the details div on and off; one extra class added as a convenience hook.

282. By Chris Johnston

[r=alanbell,mhall119] Fixes the issue where etherpad SSO breaks summit.

283. By Stuart Langridge

[r=mhall119] More mobile tweaks!

284. By Michael Hall

[r=chrisjohnston] Replaces jorge with mhall119 as email contact.

285. By Michael Hall

[r=chrisjohnston] Show multi-slot meetings in subsequent slots in the new schedule. Show track names in normal display, but not in mobile, on the new schedule

286. By Chris Johnston

Version 1.0.4 release

287. By Chris Johnston

[r=mhall119] Removes an empty if statement

288. By Chris Johnston

[r=chrisjohnston,mhall119] Changes the links to match the links on connect when viewed at summit.linaro.org

289. By Chris Johnston

[r=danilo] Adds custom QR codes for displaying links to different apps per summit

290. By Chris Johnston

[r=danilo] Adds a link to the blueprint (if there is on) in the sub-nav of the meeting page

291. By Chris Johnston

Makes it to where links on a page viewed _popup=1 contain _popup=1 in their url

292. By Chris Johnston

Makes the meeting page open up in a new window on s.l.o agenda page

293. By Chris Johnston

Version 1.0.5 release

294. By Michael Hall

[r=chrisjohnston] * Removes broken javascript for "Hide talks that aren't for me"
* Fixes schedule layout on the summit page to avoid excessive scrolling

295. By Michael Hall

[r=chrisjohnston] Limit the list of attendees the participant inline to those for the same summit as the meeting being viewed

296. By Chris Johnston

Displays everything on the user page

297. By Chris Johnston

Adds if around mugshot

298. By Chris Johnston

update

Unmerged revisions

298. By Chris Johnston

update

297. By Chris Johnston

Adds if around mugshot

296. By Chris Johnston

Displays everything on the user page

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'summit/common/forms.py'
2--- summit/common/forms.py 1970-01-01 00:00:00 +0000
3+++ summit/common/forms.py 2012-02-07 17:38:18 +0000
4@@ -0,0 +1,40 @@
5+from django.template import Context, loader
6+from django import forms
7+
8+# Taken from http://djangosnippets.org/snippets/1732/
9+class RenderableMixin(object):
10+ """
11+ Mixin to render forms from a predefined template
12+ """
13+
14+ @property
15+ def form_class_name(self):
16+ return '.'.join([self.__module__, self.__class__.__name__.lower()])
17+
18+ def as_template(self):
19+ """
20+ Renders a form from a template
21+ """
22+ self.template_name = self.__class__.__name__.lower()
23+
24+ if not getattr(self, 'tpl', None):
25+ self.tpl = loader.get_template('form.html')
26+
27+ context_dict = dict(
28+ non_field_errors=self.non_field_errors(),
29+ fields=[ forms.forms.BoundField(self, field, name) for name, field in self.fields.iteritems()],
30+ errors=self.errors,
31+ data=self.data,
32+ form=self,
33+ )
34+
35+ if getattr(self, 'initial', None):
36+ context_dict.update(dict(initial=self.initial))
37+ if getattr(self, 'instance', None):
38+ context_dict.update(dict(instance=self.instance))
39+ if getattr(self, 'cleaned_data', None):
40+ context_dict.update(dict(cleaned_data=self.cleaned_data))
41+
42+ return self.tpl.render(
43+ Context(context_dict)
44+ )
45
46=== renamed file 'summit/common/forms.py' => 'summit/common/forms.py.moved'
47=== renamed file 'summit/schedule/launchpad.py' => 'summit/common/launchpad.py'
48--- summit/schedule/launchpad.py 2012-01-23 01:18:55 +0000
49+++ summit/common/launchpad.py 2012-02-07 17:38:18 +0000
50@@ -71,3 +71,30 @@
51 openid_assoc.save()
52 return True
53 return False
54+
55+def get_mugshot_url(lp, identity):
56+ # Not ideal, but until LP #336943
57+ # or similar, we are in a hard spot.
58+ # When this bug is fixed, this can be made cleaner.
59+ try:
60+ lp.people[identity].mugshot.open()
61+ return lp.people[identity].mugshot_link or "https://api.launchpad.net/1.0/~%s/mugshot" % (identity)
62+ except HTTPError:
63+ # 404 or some other issue that means we should default to False
64+ return "https://api.launchpad.net/1.0/ubuntu/mugshot"
65+
66+def get_user_timezone(username, lp=None):
67+ timezone = 'UTC'
68+ if username is None:
69+ return timezone
70+
71+ if not lp:
72+ lp = lp_login()
73+ if not lp:
74+ return timezone
75+ try:
76+ lp_user = lp.people[username]
77+ timezone = lp_user.timezone
78+ except:
79+ pass
80+ return timezone
81
82=== modified file 'summit/schedule/models/summitmodel.py'
83--- summit/schedule/models/summitmodel.py 2012-02-02 15:27:54 +0000
84+++ summit/schedule/models/summitmodel.py 2012-02-07 17:38:18 +0000
85@@ -33,7 +33,7 @@
86 from django.contrib.auth.models import User
87
88 from summit.schedule.fields import NameField
89-from summit.schedule import launchpad
90+from summit.common import launchpad
91
92 __all__ = (
93 'Summit',
94
95=== modified file 'summit/schedule/tests.py'
96--- summit/schedule/tests.py 2012-01-25 08:32:48 +0000
97+++ summit/schedule/tests.py 2012-02-07 17:38:18 +0000
98@@ -33,8 +33,10 @@
99 from summit.schedule.fields import NameField
100
101 from summit.schedule.models import *
102+
103 from summit.schedule.render import Schedule
104-from summit.schedule import launchpad
105+
106+from summit.common import launchpad
107
108 # Monkey-patch our NameField into the types of fields that the factory
109 # understands. This is simpler than trying to subclass the Mommy
110
111=== modified file 'summit/settings.py'
112--- summit/settings.py 2012-02-02 14:16:45 +0000
113+++ summit/settings.py 2012-02-07 17:38:18 +0000
114@@ -153,6 +153,7 @@
115 'summit.schedule',
116 'summit.sponsor',
117 'summit.common',
118+ 'summit.userprofiles',
119 'south',
120 ]
121
122
123=== modified file 'summit/urls.py'
124--- summit/urls.py 2012-01-23 01:18:55 +0000
125+++ summit/urls.py 2012-02-07 17:38:18 +0000
126@@ -88,6 +88,11 @@
127 (r'^(?P<summit_name>[\w-]+)/track/(?P<track_slug>[%+\.\w-]+).ical$', 'track_ical'),
128 )
129
130+urlpatterns += patterns('summit.userprofiles.views',
131+ (r'^u/(?P<username>[%+\.\w-]+)/update$', 'update_profile'),
132+ (r'^u/(?P<username>[%+\.\w-]+)', 'user_profile'),
133+ )
134+
135 if settings.DEBUG or settings.SERVE_STATIC:
136 urlpatterns += patterns('',
137 (r'^media/(?P<path>.*)$', 'django.views.static.serve',
138
139=== added directory 'summit/userprofiles'
140=== added file 'summit/userprofiles/__init__.py'
141=== added file 'summit/userprofiles/admin.py'
142--- summit/userprofiles/admin.py 1970-01-01 00:00:00 +0000
143+++ summit/userprofiles/admin.py 2012-02-07 17:38:18 +0000
144@@ -0,0 +1,7 @@
145+from django.contrib import admin
146+from userprofiles.models import UserProfile
147+
148+class UserProfileAdmin(admin.ModelAdmin):
149+ search_fields = ('realname',)
150+
151+admin.site.register(UserProfile, UserProfileAdmin)
152
153=== added file 'summit/userprofiles/forms.py'
154--- summit/userprofiles/forms.py 1970-01-01 00:00:00 +0000
155+++ summit/userprofiles/forms.py 2012-02-07 17:38:18 +0000
156@@ -0,0 +1,25 @@
157+# Ubuntu Developer Summit web application
158+# Copyright (C) 2008, 2009, 2010 Canonical Ltd
159+#
160+# This program is free software: you can redistribute it and/or modify
161+# it under the terms of the GNU Affero General Public License as
162+# published by the Free Software Foundation, either version 3 of the
163+# License, or (at your option) any later version.
164+#
165+# This program is distributed in the hope that it will be useful,
166+# but WITHOUT ANY WARRANTY; without even the implied warranty of
167+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
168+# GNU Affero General Public License for more details.
169+#
170+# You should have received a copy of the GNU Affero General Public License
171+# along with this program. If not, see <http://www.gnu.org/licenses/>.
172+
173+from django import forms
174+
175+from common.forms import RenderableMixin
176+from summit.userprofiles.models import UserProfile
177+
178+class UpdateProfileForm(forms.ModelForm, RenderableMixin):
179+ class Meta:
180+ model = UserProfile
181+ exclude = ('user', 'tz')
182
183=== added directory 'summit/userprofiles/management'
184=== added file 'summit/userprofiles/management/__init__.py'
185=== added directory 'summit/userprofiles/management/commands'
186=== added file 'summit/userprofiles/management/commands/__init__.py'
187=== added file 'summit/userprofiles/management/commands/update-profiles.py'
188--- summit/userprofiles/management/commands/update-profiles.py 1970-01-01 00:00:00 +0000
189+++ summit/userprofiles/management/commands/update-profiles.py 2012-02-07 17:38:18 +0000
190@@ -0,0 +1,39 @@
191+#!/usr/bin/python
192+# -*- coding: utf-8 -*-
193+
194+from django.core.management.base import NoArgsCommand
195+
196+from common import launchpad
197+from django.contrib.auth.models import User, Group
198+from userprofiles.models import UserProfile, create_profile
199+
200+from datetime import datetime
201+import sys
202+
203+class Command(NoArgsCommand):
204+ help = "Updates user profiles with information from Launchpad."
205+
206+ def handle_noargs(self, **options):
207+ lp = launchpad.lp_login()
208+ if not lp:
209+ sys.exit(1)
210+ USER_BLACKLIST = (u"root", u"admin")
211+
212+ summit_users = User.objects.all()
213+
214+ for summit_user in summit_users:
215+ if not summit_user.username in USER_BLACKLIST:
216+ profile, created = UserProfile.objects.get_or_create(user=summit_user)
217+ try:
218+ lp_user = lp.people[summit_user.username]
219+ profile.tz = lp_user.time_zone or "UTC"
220+ profile.realname = lp_user.display_name or user.username
221+ if lp_user.logo_link.startswith("https://") :
222+ profile.mugshot = lp_user.logo_link
223+ else:
224+ profile.mugshot = "https://launchpad.net/@@/person-logo"
225+ profile.save()
226+ except Exception, e:
227+ print "Error updating %s" % summit_user
228+ print e
229+ pass
230
231=== added directory 'summit/userprofiles/migrations'
232=== added file 'summit/userprofiles/migrations/0001_add_user_profiles.py'
233--- summit/userprofiles/migrations/0001_add_user_profiles.py 1970-01-01 00:00:00 +0000
234+++ summit/userprofiles/migrations/0001_add_user_profiles.py 2012-02-07 17:38:18 +0000
235@@ -0,0 +1,93 @@
236+# encoding: utf-8
237+import datetime
238+from south.db import db
239+from south.v2 import SchemaMigration
240+from django.db import models
241+
242+class Migration(SchemaMigration):
243+
244+ def forwards(self, orm):
245+
246+ # Adding model 'UserProfile'
247+ db.create_table('userprofiles_userprofile', (
248+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
249+ ('user', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['auth.User'], unique=True)),
250+ ('realname', self.gf('django.db.models.fields.CharField')(max_length=150, blank=True)),
251+ ('tz', self.gf('django.db.models.fields.CharField')(default='UTC', max_length=32)),
252+ ('mugshot', self.gf('django.db.models.fields.URLField')(max_length=150, null=True, blank=True)),
253+ ('blog', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)),
254+ ('twitter', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
255+ ('identica', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
256+ ('picasa', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
257+ ('flickr', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
258+ ('facebook', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
259+ ('irc', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
260+ ('aim', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
261+ ('xmpp', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
262+ ))
263+ db.send_create_signal('userprofiles', ['UserProfile'])
264+
265+
266+ def backwards(self, orm):
267+
268+ # Deleting model 'UserProfile'
269+ db.delete_table('userprofiles_userprofile')
270+
271+
272+ models = {
273+ 'auth.group': {
274+ 'Meta': {'object_name': 'Group'},
275+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
276+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
277+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
278+ },
279+ 'auth.permission': {
280+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
281+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
282+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
283+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
284+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
285+ },
286+ 'auth.user': {
287+ 'Meta': {'object_name': 'User'},
288+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
289+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
290+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
291+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
292+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
293+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
294+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
295+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
296+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
297+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
298+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
299+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
300+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
301+ },
302+ 'contenttypes.contenttype': {
303+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
304+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
305+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
306+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
307+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
308+ },
309+ 'userprofiles.userprofile': {
310+ 'Meta': {'ordering': "('user__username',)", 'object_name': 'UserProfile'},
311+ 'aim': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
312+ 'blog': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
313+ 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
314+ 'flickr': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
315+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
316+ 'identica': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
317+ 'irc': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
318+ 'mugshot': ('django.db.models.fields.URLField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}),
319+ 'picasa': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
320+ 'realname': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}),
321+ 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
322+ 'tz': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '32'}),
323+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}),
324+ 'xmpp': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'})
325+ }
326+ }
327+
328+ complete_apps = ['userprofiles']
329
330=== added file 'summit/userprofiles/migrations/__init__.py'
331=== added file 'summit/userprofiles/models.py'
332--- summit/userprofiles/models.py 1970-01-01 00:00:00 +0000
333+++ summit/userprofiles/models.py 2012-02-07 17:38:18 +0000
334@@ -0,0 +1,82 @@
335+from django.db import models
336+from django.utils.translation import ugettext_lazy as _
337+from django.contrib.auth import models as auth_models
338+
339+
340+import pytz
341+
342+class UserProfile(models.Model):
343+ " Store profile information about a user "
344+
345+ user = models.OneToOneField(auth_models.User)
346+ realname = models.CharField(_("Real Name"), max_length=150, blank=True)
347+ tz = models.CharField(verbose_name=_('Timezone'), max_length=32, default='UTC')
348+ mugshot = models.URLField(verbose_name=_("Mugshot"), max_length=150, blank=True, verify_exists=False, null=True)
349+ blog = models.URLField(verbose_name=_('Blog URL'), blank=True, null=True, verify_exists=False)
350+ twitter = models.CharField(verbose_name=_('Twitter ID'), max_length=32, blank=True, null=True)
351+ identica = models.CharField(verbose_name=_('Identi.ca ID'), max_length=32, blank=True, null=True)
352+ picasa = models.CharField(verbose_name=_('Picasa ID'), max_length=32, blank=True, null=True)
353+ flickr = models.CharField(verbose_name=_('Flickr ID'), max_length=32, blank=True, null=True)
354+ facebook = models.CharField(verbose_name=_('Facebook ID'), max_length=32, blank=True, null=True)
355+ irc = models.CharField(verbose_name=_('IRC Nick'), max_length=32, blank=True, null=True)
356+ aim = models.CharField(verbose_name=_('AOL IM Nick'), max_length=32, blank=True, null=True)
357+ xmpp = models.CharField(verbose_name=_('XMPP IM Nick'), max_length=32, blank=True, null=True)
358+
359+ class Meta:
360+ ordering = ('user__username',)
361+
362+ def __unicode__(self):
363+ try:
364+ if self.realname:
365+ return "%s (%s)" % (self.user.username, self.realname)
366+ return "%s" % self.user.username
367+ except:
368+ return "Unknown Profile"
369+
370+ def get_timezone(self):
371+ try:
372+ return pytz.timezone(self.tz)
373+ except:
374+ return pytz.utc
375+ timezone = property(get_timezone)
376+
377+ def tolocaltime(self, dt):
378+ as_utc = pytz.utc.localize(dt)
379+ return as_utc.astimezone(self.timezone)
380+
381+ def fromlocaltime(self, dt):
382+ local = self.timezone.localize(dt)
383+ return local.astimezone(pytz.utc)
384+
385+def _getUserProfile(self):
386+ if self.is_anonymous():
387+ return UserProfile()
388+
389+ profile, created = UserProfile.objects.get_or_create(user=self)
390+
391+ if created:
392+ from common.launchpad import get_user_timezone
393+ profile.tz = get_user_timezone(self.username)
394+ profile.save()
395+
396+ return profile
397+
398+def _getAnonProfile(self):
399+ return UserProfile()
400+
401+auth_models.User.profile = property(_getUserProfile)
402+auth_models.AnonymousUser.profile = property(_getAnonProfile)
403+
404+def create_profile(username):
405+ user, created = auth_models.User.objects.get_or_create(username=username)
406+ if created:
407+ user.save()
408+ from common import launchpad
409+ launchpad.set_user_openid(user, force=True)
410+ profile, created = UserProfile.objects.get_or_create(user=user)
411+ if created:
412+ # set real name as username for now,
413+ # we get the name later on via cronjob
414+ profile.realname = username
415+ profile.save()
416+ return profile
417
418=== added directory 'summit/userprofiles/templates'
419=== added directory 'summit/userprofiles/templates/userprofiles'
420=== added file 'summit/userprofiles/templates/userprofiles/profile.html'
421--- summit/userprofiles/templates/userprofiles/profile.html 1970-01-01 00:00:00 +0000
422+++ summit/userprofiles/templates/userprofiles/profile.html 2012-02-07 17:38:18 +0000
423@@ -0,0 +1,43 @@
424+{% extends "base.html" %}
425+
426+{% block page_name %}
427+{{ display_user.profile.realname }}
428+{% endblock %}
429+{% block sub_nav_links %}
430+<a class="sub-nav-item" href="">Update</a>
431+{% endblock %}
432+{% block content %}
433+<h3>User: {{ display_user.profile.realname }}</h3>
434+{% if display_user.profile.mugshot %}<img src="{{ display_user.profile.mugshot }}" alt="{{ display_user.profile.realname }}" /><br />{% endif %}
435+Launchpad ID: {{ display_user.profile.user }}<br />
436+{% if display_user.profile.tz %}
437+Timezone: {{ display_user.profile.tz }}<br />
438+{% endif %}
439+{% if display_user.profile.irc %}
440+IRC Nick: {{ display_user.profile.irc }} <br />
441+{% endif %}
442+{% if display_user.profile.blog %}
443+Blog: <a href="{{ display_user.profile.blog }}">{{ display_user.profile.blog }}</a><br />
444+{% endif %}
445+{% if display_user.profile.twitter %}
446+Twitter: <a href="http://twitter.com/{{ display_user.profile.twitter }}">{{ display_user.profile.twitter }}</a><br />
447+{% endif %}
448+{% if display_user.profile.facebook %}
449+Facebook: <a href="http://facebook.com/{{ display_user.profile.facebook }}">{{ display_user.profile.facebook }}</a><br />
450+{% endif %}
451+{% if display_user.profile.identia %}
452+Identi.ca: {{ display_user.profile.identica }}<br />
453+{% endif %}
454+{% if display_user.profile.picasa %}
455+Picasa: {{ display_user.profile.picasa }}<br />
456+{% endif %}
457+{% if display_user.profile.flickr %}
458+Flickr: {{ display_user.profile.flickr }}<br />
459+{% endif %}
460+{% if display_user.profile.aim %}
461+AOL IM: {{ display_user.profile.aim }}<br />
462+{% endif %}
463+{% if display_user.profile.xmpp %}
464+XMPP: {{ display_user.profile.xmpp }}
465+{% endif %}
466+{% endblock %}
467
468=== added file 'summit/userprofiles/templates/userprofiles/profile_update.html'
469--- summit/userprofiles/templates/userprofiles/profile_update.html 1970-01-01 00:00:00 +0000
470+++ summit/userprofiles/templates/userprofiles/profile_update.html 2012-02-07 17:38:18 +0000
471@@ -0,0 +1,41 @@
472+{% extends "base.html" %}
473+
474+{% block extrafooter %}
475+<script type="text/javascript"><!--
476+$(document).ready(function(){
477+ $('span[rel*=help]').colorTip({color:'orange'});
478+});
479+--></script>
480+{% endblock %}
481+
482+
483+{% block content %}
484+
485+ <article id="form" class="main-content">
486+ {% if form.errors %}
487+ <p style="color: red;">
488+ Please correct the error{{ form.errors|pluralize }} below.
489+ </p>
490+ {% endif %}
491+
492+ <form action="{{ request.path_info }}" method="POST">
493+ <fieldset>
494+ <legend>User information</legend>
495+ <div>
496+ <div class="field"><label>Username:</label></div>
497+ <span class="extra">{{ display_user.profile.user }}</span>
498+ </div>
499+ <div>
500+ <div class="field"><label>Timezone:</label></div>
501+ <span class="extra">{{ display_user.profile.tz }}</span>
502+ </div>
503+ </fieldset>
504+ <fieldset>
505+ <legend>Update profile information below</legend>
506+ {{ form.as_template }}
507+ </fieldset>
508+ {% if is_popup %}<input type="hidden" name="_popup" value="1">{% endif %}
509+ <input type="submit" name="submit" value="Update!" class="submit-button" />
510+ </form>
511+ </article>
512+{% endblock %}
513
514=== added file 'summit/userprofiles/tests.py'
515--- summit/userprofiles/tests.py 1970-01-01 00:00:00 +0000
516+++ summit/userprofiles/tests.py 2012-02-07 17:38:18 +0000
517@@ -0,0 +1,16 @@
518+"""
519+This file demonstrates writing tests using the unittest module. These will pass
520+when you run "manage.py test".
521+
522+Replace this with more appropriate tests for your application.
523+"""
524+
525+from django.test import TestCase
526+
527+
528+class SimpleTest(TestCase):
529+ def test_basic_addition(self):
530+ """
531+ Tests that 1 + 1 always equals 2.
532+ """
533+ self.assertEqual(1 + 1, 2)
534
535=== added file 'summit/userprofiles/views.py'
536--- summit/userprofiles/views.py 1970-01-01 00:00:00 +0000
537+++ summit/userprofiles/views.py 2012-02-07 17:38:18 +0000
538@@ -0,0 +1,66 @@
539+# Ubuntu Developer Summit web application
540+# Copyright (C) 2008, 2009, 2010 Canonical Ltd
541+#
542+# This program is free software: you can redistribute it and/or modify
543+# it under the terms of the GNU Affero General Public License as
544+# published by the Free Software Foundation, either version 3 of the
545+# License, or (at your option) any later version.
546+#
547+# This program is distributed in the hope that it will be useful,
548+# but WITHOUT ANY WARRANTY; without even the implied warranty of
549+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
550+# GNU Affero General Public License for more details.
551+#
552+# You should have received a copy of the GNU Affero General Public License
553+# along with this program. If not, see <http://www.gnu.org/licenses/>.
554+
555+from django.conf import settings
556+from django.http import HttpResponse, HttpResponseRedirect
557+from django.shortcuts import render_to_response, get_object_or_404
558+from django.template import RequestContext
559+from django.contrib.auth.models import User
560+
561+from common.utils import redirect
562+
563+import forms
564+
565+from summit.userprofiles.models import UserProfile
566+
567+__all__ = (
568+ 'user_profile',
569+)
570+
571+def user_profile(request, username):
572+ user = get_object_or_404(User, username=username)
573+
574+ context = {
575+ 'display_user': user,
576+ 'display_user_profile': user.profile,
577+ }
578+ return render_to_response("userprofiles/profile.html", context,
579+ context_instance=RequestContext(request))
580+
581+def update_profile(request, username):
582+ user = get_object_or_404(User, username=username)
583+
584+ if request.user.id != user.id:
585+ request.user.message_set.create(message='You are not allowed to make changes to this profile.')
586+ return redirect( user_profile, username )
587+
588+ if request.method == "POST":
589+ form = forms.UpdateProfileForm(data=request.POST, instance=user.profile)
590+ if form.is_valid():
591+ form.save()
592+ request.user.message_set.create(message='User profile updated')
593+ return redirect( user_profile, username )
594+ else:
595+ request.user.message_set.create(message='User profile could not be saved.')
596+ else:
597+ form = forms.UpdateProfileForm(instance=user.profile)
598+
599+
600+ return render_to_response('userprofiles/profile_update.html',
601+ {'form': form,
602+ 'display_user': user,
603+ 'display_user_profile': user.profile,
604+ }, RequestContext(request))

Subscribers

People subscribed via source and target branches