|
1 from cm.converters.pandoc_converters import \ |
|
2 CHOICES_INPUT_FORMATS as CHOICES_INPUT_FORMATS_PANDOC, \ |
|
3 DEFAULT_INPUT_FORMAT as DEFAULT_INPUT_FORMAT_PANDOC, pandoc_convert |
|
4 from cm.models_base import PermanentModel, KeyManager, Manager, KeyModel, AuthorModel |
|
5 from cm.models_utils import * |
|
6 from cm.utils.dj import absolute_reverse |
|
7 from cm.utils.date import datetime_to_user_str |
|
8 from cm.utils.comment_positioning import compute_new_comment_positions |
|
9 from django import forms |
|
10 from django.db.models import Q |
|
11 from django.template.loader import render_to_string |
|
12 from django.conf import settings |
|
13 from django.template import RequestContext |
|
14 from django.contrib.auth.models import Permission |
|
15 from django.contrib.contenttypes import generic |
|
16 from django.contrib.contenttypes.models import ContentType |
|
17 from django.core.files.base import ContentFile |
|
18 from django.core.urlresolvers import reverse |
|
19 from django.template.defaultfilters import timesince |
|
20 from django.db import models |
|
21 from django.utils.translation import ugettext as _, ugettext_lazy, ugettext_noop |
|
22 from tagging.fields import TagField |
|
23 import pickle |
|
24 from django.db import connection |
|
25 |
|
26 |
|
27 |
|
28 class TextManager(Manager): |
|
29 def create_text(self, title, format, content, note, name, email, tags, user=None, state='approved', **kwargs): |
|
30 text = self.create(name=name, email=email, user=user, state=state) |
|
31 text_version = TextVersion.objects.create(title=title, format=format, content=content, text=text, note=note, name=name, email=email, tags=tags, user=user) |
|
32 return text |
|
33 |
|
34 def create_new_version(self, text, title, format, content, note, name, email, tags, user=None, **kwargs): |
|
35 text_version = TextVersion.objects.create(title=title, format=format, content=content, text=text, note=note, name=name, email=email, tags=tags, user=user) |
|
36 return text_version |
|
37 |
|
38 class Text(PermanentModel, AuthorModel): |
|
39 modified = models.DateTimeField(auto_now=True) |
|
40 created = models.DateTimeField(auto_now_add=True) |
|
41 |
|
42 private_feed_key = models.CharField(max_length=20, db_index=True, unique=True, blank=True, null=True, default=None) |
|
43 |
|
44 # denormalized fields |
|
45 last_text_version = models.ForeignKey("TextVersion", related_name='related_text', null=True, blank=True) |
|
46 title = models.TextField() |
|
47 |
|
48 objects = TextManager() |
|
49 |
|
50 def get_latest_version(self): |
|
51 return self.last_text_version |
|
52 |
|
53 def fetch_latest_version(self): |
|
54 versions = self.get_versions() |
|
55 if versions: |
|
56 return versions[0] |
|
57 else: |
|
58 return None |
|
59 |
|
60 def update_denorm_fields(self): |
|
61 real_last_text_version = self.fetch_latest_version() |
|
62 |
|
63 modif = False |
|
64 if real_last_text_version and real_last_text_version != self.last_text_version: |
|
65 self.last_text_version = real_last_text_version |
|
66 modif = True |
|
67 |
|
68 if real_last_text_version and real_last_text_version.title and real_last_text_version.title != self.title: |
|
69 self.title = real_last_text_version.title |
|
70 modif = True |
|
71 |
|
72 if real_last_text_version and real_last_text_version.modified != self.modified: |
|
73 self.modified = real_last_text_version.modified |
|
74 modif = True |
|
75 |
|
76 if modif: |
|
77 self.save() |
|
78 |
|
79 |
|
80 def get_title(self): |
|
81 return self.get_latest_version().title |
|
82 |
|
83 def get_versions(self): |
|
84 """ |
|
85 Versions with most recent first |
|
86 """ |
|
87 versions = TextVersion.objects.filter(text__exact=self).order_by('-created') |
|
88 # TODO: use new postgresql 8.4 row_number as extra select to do that |
|
89 for index in xrange(len(versions)): |
|
90 v = versions[index] |
|
91 # version_number is 1-based |
|
92 setattr(v, 'version_number', len(versions) - index) |
|
93 #for v in versions: |
|
94 # print v.created,v.id,v.version_number |
|
95 return versions |
|
96 |
|
97 def get_version(self, version_number): |
|
98 """ |
|
99 Get version number 'version_number' (1-based) |
|
100 """ |
|
101 version = TextVersion.objects.filter(text__exact=self).order_by('created')[version_number - 1:version_number][0] |
|
102 return version |
|
103 |
|
104 def get_inversed_versions(self): |
|
105 versions = TextVersion.objects.filter(text__exact=self).order_by('created') |
|
106 # TODO: use new postgresql 8.4 row_number as extra select to do that |
|
107 for index in xrange(len(versions)): |
|
108 v = versions[index] |
|
109 # version_number is 1-based |
|
110 setattr(v, 'version_number', index + 1) |
|
111 return versions |
|
112 |
|
113 def get_versions_number(self): |
|
114 return self.get_versions().count() |
|
115 |
|
116 def is_admin(self, adminkey=None): |
|
117 if adminkey and self.adminkey == adminkey: |
|
118 return True |
|
119 else: |
|
120 return False |
|
121 |
|
122 def revert_to_version(self, v_id): |
|
123 text_version = self.get_version(int(v_id)) |
|
124 new_text_version = TextVersion.objects.duplicate(text_version, True) |
|
125 return new_text_version |
|
126 |
|
127 def edit(self, new_title, new_format, new_content, new_tags=None, new_note=None, keep_comments=True, new_version=True): |
|
128 text_version = self.get_latest_version() |
|
129 |
|
130 if new_version: |
|
131 text_version = TextVersion.objects.duplicate(text_version, keep_comments) |
|
132 text_version.edit(new_title, new_format, new_content, new_tags, new_note, keep_comments) |
|
133 return text_version |
|
134 |
|
135 def __unicode__(self): |
|
136 return self.title |
|
137 |
|
138 DEFAULT_INPUT_FORMAT = getattr(settings, 'DEFAULT_INPUT_FORMAT', DEFAULT_INPUT_FORMAT_PANDOC) |
|
139 CHOICES_INPUT_FORMATS = getattr(settings, 'CHOICES_INPUT_FORMATS', CHOICES_INPUT_FORMATS_PANDOC) |
|
140 |
|
141 class TextVersionManager(models.Manager): |
|
142 |
|
143 def duplicate(self, text_version, duplicate_comments=True): |
|
144 #import pdb;pdb.set_trace() |
|
145 old_comment_set = set(text_version.comment_set.all()) |
|
146 text_version.id = None |
|
147 #import pdb;pdb.set_trace() |
|
148 text_version.save() |
|
149 |
|
150 duplicate_text_version = text_version |
|
151 |
|
152 if duplicate_comments: |
|
153 old_comment_map = {} |
|
154 while len(old_comment_set): |
|
155 for c in old_comment_set: |
|
156 if not c.reply_to or c.reply_to.id in old_comment_map: |
|
157 old_id = c.id |
|
158 old_comment_set.remove(c) |
|
159 reply_to = None |
|
160 if c.reply_to: |
|
161 reply_to = old_comment_map[c.reply_to.id] |
|
162 c2 = Comment.objects.duplicate(c, duplicate_text_version, reply_to) |
|
163 old_comment_map[old_id] = c2 |
|
164 break |
|
165 |
|
166 return duplicate_text_version |
|
167 |
|
168 class TextVersion(AuthorModel): |
|
169 modified = models.DateTimeField(auto_now=True) |
|
170 created = models.DateTimeField(auto_now_add=True) |
|
171 |
|
172 title = models.TextField(ugettext_lazy("Title")) |
|
173 format = models.CharField(ugettext_lazy("Format"), max_length=20, blank=False, default=DEFAULT_INPUT_FORMAT, choices=CHOICES_INPUT_FORMATS) |
|
174 content = models.TextField(ugettext_lazy("Content")) |
|
175 tags = TagField(ugettext_lazy("Tags"), max_length=1000) |
|
176 |
|
177 note = models.CharField(ugettext_lazy("Note"), max_length=100, null=True, blank=True) |
|
178 |
|
179 mod_posteriori = models.BooleanField(ugettext_lazy('Moderation a posteriori?'), default=True) |
|
180 |
|
181 text = models.ForeignKey("Text") |
|
182 |
|
183 objects = TextVersionManager() |
|
184 |
|
185 def get_content(self, format='html'): |
|
186 converted_content = pandoc_convert(self.content, self.format, format) |
|
187 return converted_content |
|
188 |
|
189 # def _get_comments(self, user = None, filter_reply = 0): |
|
190 # """ |
|
191 # get comments viewable by this user (user = None or user = AnonymousUser => everyone) |
|
192 # filter_reply = 0: comments and replies |
|
193 # 1: comments |
|
194 # 2: replies |
|
195 # """ |
|
196 # from cm.security import has_perm_on_text # should stay here to avoid circular dependencies |
|
197 # |
|
198 # if has_perm(user, 'can_view_unapproved_comment', self.text): |
|
199 # comments = self.comment_set.all() |
|
200 # elif has_perm(user, 'can_view_approved_comment', self.text): |
|
201 # comments = self.comment_set.filter(visible=True) |
|
202 # elif has_perm(user, 'can_view_own_comment', self.text): |
|
203 # comments = self.comment_set.filter(user=user) |
|
204 # else: |
|
205 # return Comment.objects.none() # empty queryset |
|
206 # if filter_reply: |
|
207 # comments = comments.filter) |
|
208 # return comments |
|
209 # |
|
210 # def get_comments_as_json(self, user = None): |
|
211 # return simplejson.dumps(self._get_comments(user, filter_reply=0)) |
|
212 # |
|
213 # def get_comments_and_replies(self, user = None): |
|
214 # return (self.get_comments(user), |
|
215 # self.get_replies(user)) |
|
216 # |
|
217 def get_comments(self): |
|
218 "Warning: data access without security" |
|
219 return self.comment_set.filter(reply_to=None, deleted=False) |
|
220 |
|
221 def get_replies(self): |
|
222 "Warning: data access without security" |
|
223 return self.comment_set.filter(~Q(reply_to == None), Q(deleted=False)) |
|
224 |
|
225 def __unicode__(self): |
|
226 return '<%d> %s' % (self.id, self.title) |
|
227 |
|
228 def edit(self, new_title, new_format, new_content, new_tags=None, new_note=None, keep_comments=True): # TODO : tags |
|
229 if not keep_comments : |
|
230 self.comment_set.all().delete() |
|
231 elif self.content != new_content or new_format != self.format: |
|
232 comments = self.get_comments() ; |
|
233 tomodify_comments, toremove_comments = compute_new_comment_positions(self.content, self.format, new_content, new_format, comments) |
|
234 #print "tomodify_comments",len(tomodify_comments) |
|
235 #print "toremove_comments",len(toremove_comments) |
|
236 [comment.save() for comment in tomodify_comments] |
|
237 [comment.delete() for comment in toremove_comments] |
|
238 self.title = new_title |
|
239 if new_tags: |
|
240 self.tags = new_tags |
|
241 if new_note: |
|
242 self.note = new_note |
|
243 self.content = new_content |
|
244 self.format = new_format |
|
245 self.save() |
|
246 |
|
247 class CommentManager(Manager): |
|
248 |
|
249 def duplicate(self, comment, text_version, reply_to=None): |
|
250 comment.id = None |
|
251 comment.text_version = text_version |
|
252 if reply_to: |
|
253 comment.reply_to = reply_to |
|
254 self.update_keys(comment) |
|
255 comment.save() |
|
256 return comment |
|
257 |
|
258 class Comment(PermanentModel, AuthorModel): |
|
259 modified = models.DateTimeField(auto_now=True) |
|
260 created = models.DateTimeField(auto_now_add=True) |
|
261 |
|
262 text_version = models.ForeignKey("TextVersion") |
|
263 |
|
264 # comment_set will be replies |
|
265 reply_to = models.ForeignKey("Comment", null=True, blank=True) |
|
266 |
|
267 title = models.TextField() |
|
268 content = models.TextField() |
|
269 content_html = models.TextField() |
|
270 |
|
271 format = models.CharField(_("Format:"), max_length=20, blank=False, default=DEFAULT_INPUT_FORMAT, choices=CHOICES_INPUT_FORMATS) |
|
272 |
|
273 tags = TagField() |
|
274 |
|
275 start_wrapper = models.IntegerField(null=True, blank=True) |
|
276 end_wrapper = models.IntegerField(null=True, blank=True) |
|
277 start_offset = models.IntegerField(null=True, blank=True) |
|
278 end_offset = models.IntegerField(null=True, blank=True) |
|
279 |
|
280 objects = CommentManager() |
|
281 |
|
282 def __unicode__(self): |
|
283 return '<%d> %s' % (self.id, self.title) |
|
284 |
|
285 def is_reply(self): |
|
286 return self.reply_to != None |
|
287 |
|
288 def is_thread_full_visible(self): |
|
289 cur_comment = self |
|
290 if not cur_comment.state == 'approved': |
|
291 return False |
|
292 |
|
293 while cur_comment.reply_to != None: |
|
294 cur_comment = cur_comment.reply_to |
|
295 if not cur_comment.state == 'approved': |
|
296 return False |
|
297 |
|
298 return True |
|
299 |
|
300 def top_comment(self): |
|
301 if self.reply_to == None : |
|
302 return self |
|
303 else : |
|
304 return self.reply_to.top_comment() |
|
305 |
|
306 def depth(self): |
|
307 if self.reply_to == None : |
|
308 return 0 |
|
309 else : |
|
310 return 1 + self.reply_to.depth() |
|
311 |
|
312 def delete(self): |
|
313 PermanentModel.delete(self) |
|
314 # delete replies |
|
315 [c.delete() for c in self.comment_set.all()] |
|
316 |
|
317 # http://docs.djangoproject.com/en/dev/topics/files/#topics-files |
|
318 |
|
319 # default conf values |
|
320 DEFAULT_CONF = { |
|
321 'workspace_name' : 'Workspace', |
|
322 'site_url' : settings.SITE_URL, |
|
323 'email_from' : settings.DEFAULT_FROM_EMAIL, |
|
324 } |
|
325 |
|
326 from cm.role_models import change_role_model |
|
327 |
|
328 class ConfigurationManager(models.Manager): |
|
329 def set_workspace_name(self, workspace_name): |
|
330 if workspace_name and not self.get_key('workspace_name')!=u'Workspace': |
|
331 self.set_key('workspace_name', _(u"%(workspace_name)s's workspace") %{'workspace_name':workspace_name}) |
|
332 |
|
333 def get_key(self, key, default_value=None): |
|
334 try: |
|
335 return self.get(key=key).value |
|
336 except Configuration.DoesNotExist: |
|
337 return DEFAULT_CONF.get(key, default_value) |
|
338 |
|
339 def set_key(self, key, value): |
|
340 conf, created = self.get_or_create(key=key) |
|
341 if created or conf.value != value: |
|
342 conf.value = value |
|
343 conf.save() |
|
344 if key == 'workspace_role_model': |
|
345 change_role_model(value) |
|
346 |
|
347 def __getitem__(self, key): |
|
348 return self.get_key(key, None) |
|
349 |
|
350 import base64 |
|
351 |
|
352 class Configuration(models.Model): |
|
353 key = models.TextField(blank=False) # , unique=True cannot be added: creates error on mysql (?) |
|
354 raw_value = models.TextField(blank=False) |
|
355 |
|
356 def get_value(self): |
|
357 return pickle.loads(base64.b64decode(self.raw_value.encode('utf8'))) |
|
358 |
|
359 def set_value(self, value): |
|
360 self.raw_value = base64.b64encode(pickle.dumps(value, 0)).encode('utf8') |
|
361 |
|
362 value = property(get_value, set_value) |
|
363 |
|
364 objects = ConfigurationManager() |
|
365 |
|
366 def __unicode__(self): |
|
367 return '%s: %s' % (self.key, self.value) |
|
368 |
|
369 ApplicationConfiguration = Configuration.objects |
|
370 |
|
371 class AttachmentManager(KeyManager): |
|
372 def create_attachment(self, text_version, filename, data): |
|
373 attach = self.create(text_version=text_version) |
|
374 ff = ContentFile(data) |
|
375 attach.data.save(filename, ff) |
|
376 return attach |
|
377 |
|
378 class Attachment(KeyModel): |
|
379 data = models.FileField(upload_to="attachments/%Y/%m/%d/", max_length=1000) |
|
380 text_version = models.ForeignKey(TextVersion) |
|
381 |
|
382 objects = AttachmentManager() |
|
383 |
|
384 class NotificationManager(KeyManager): |
|
385 def create_notification(self, text, type, email_or_user): |
|
386 prev_notification = self.get_notification_to_own_discussions(text, type, email_or_user) |
|
387 if not prev_notification: |
|
388 notification = self.create(text=text, type=type) |
|
389 notification.set_email_or_user(email_or_user) |
|
390 return notification |
|
391 else: |
|
392 return prev_notification |
|
393 |
|
394 def get_notification_to_own_discussions(self, text, type, email_or_user): |
|
395 if isinstance(email_or_user,unicode): |
|
396 prev_notifications = Notification.objects.filter(text=text, type=type, email=email_or_user) |
|
397 else: |
|
398 prev_notifications = Notification.objects.filter(text=text, type=type, user=email_or_user) |
|
399 if prev_notifications: |
|
400 return prev_notifications[0] |
|
401 else: |
|
402 return None |
|
403 |
|
404 def set_notification_to_own_discussions(self, text, email_or_user, active=True): |
|
405 if active: |
|
406 notification = self.create_notification(text, 'own', email_or_user) |
|
407 if not notification.active: |
|
408 notification.active = True |
|
409 notification.save() |
|
410 else: |
|
411 notification = self.create_notification(text, 'own', email_or_user) |
|
412 notification.active = False |
|
413 notification.save() |
|
414 |
|
415 def subscribe_to_own_text(self, text, user): |
|
416 return self.create_notification(text, None, user) |
|
417 |
|
418 class Notification(KeyModel, AuthorModel): |
|
419 text = models.ForeignKey(Text, null=True, blank=True) |
|
420 type = models.CharField(max_length=30, null=True, blank=True) |
|
421 active = models.BooleanField(default=True) # active = False means user desactivation |
|
422 |
|
423 objects = NotificationManager() |
|
424 |
|
425 def desactivate_notification_url(self): |
|
426 return reverse('desactivate-notification', args=[self.adminkey]) |
|
427 |
|
428 def desactivate(self): |
|
429 if self.type=='own': |
|
430 self.active = False |
|
431 self.save() |
|
432 else: |
|
433 self.delete() |
|
434 |
|
435 # right management |
|
436 class UserRoleManager(models.Manager): |
|
437 def create_userroles_text(self, text): |
|
438 # make sure every user has a userrole on this text |
|
439 for user in User.objects.all(): |
|
440 userrole, _ = self.get_or_create(user=user, text=text) |
|
441 # anon user |
|
442 userrole, _ = self.get_or_create(user=None, text=text) |
|
443 # anon global user |
|
444 global_userrole, _ = self.get_or_create(user=None, text=None) |
|
445 |
|
446 class UserRole(models.Model): |
|
447 role = models.ForeignKey("Role", null=True, blank=True) |
|
448 |
|
449 # user == null => anyone |
|
450 user = models.ForeignKey(User, null=True, blank=True) |
|
451 |
|
452 # text == null => any text (workspace role) |
|
453 text = models.ForeignKey(Text, null=True, blank=True) |
|
454 |
|
455 objects = UserRoleManager() |
|
456 |
|
457 class Meta: |
|
458 unique_together = (('role', 'user', 'text',)) |
|
459 |
|
460 def __unicode__(self): |
|
461 if self.role: |
|
462 rolename = _(self.role.name) |
|
463 else: |
|
464 rolename = '' |
|
465 |
|
466 if self.user: |
|
467 return u"%s: %s %s %s" % (self.__class__.__name__, self.user.username, self.text, rolename) |
|
468 else: |
|
469 return u"%s: *ALL* %s %s" % (self.__class__.__name__, self.text, rolename) |
|
470 |
|
471 def __repr__(self): |
|
472 return self.__unicode__() |
|
473 |
|
474 from cm.models_base import generate_key |
|
475 from cm.utils.misc import update |
|
476 |
|
477 class Role(models.Model): |
|
478 """ |
|
479 'Static' application roles |
|
480 """ |
|
481 name = models.CharField(ugettext_lazy('name'), max_length=50, unique=True) |
|
482 description = models.TextField(ugettext_lazy('description')) |
|
483 #order = models.IntegerField(unique=True) |
|
484 permissions = models.ManyToManyField(Permission, related_name="roles") |
|
485 |
|
486 global_scope = models.BooleanField('global scope', default=False) # applies to global scope only |
|
487 anon = models.BooleanField('anonymous', default=False) # role possible for anonymous users? |
|
488 |
|
489 def __unicode__(self): |
|
490 return _(self.name) |
|
491 |
|
492 def __hash__(self): |
|
493 return self.id |
|
494 |
|
495 def name_i18n(self): |
|
496 return _(self.name) |
|
497 |
|
498 from django.utils.safestring import mark_safe |
|
499 |
|
500 class RegistrationManager(KeyManager): |
|
501 def activate_user(self, activation_key): |
|
502 """ |
|
503 Validates an activation key and activates the corresponding |
|
504 ``User`` if valid. |
|
505 If the key is valid , returns the ``User`` as second arg |
|
506 First is boolean indicating if user has just been activated |
|
507 """ |
|
508 # Make sure the key we're trying conforms to the pattern of a |
|
509 # SHA1 hash; if it doesn't, no point trying to look it up in |
|
510 # the database. |
|
511 try: |
|
512 profile = self.get(admin_key=activation_key) |
|
513 except self.model.DoesNotExist: |
|
514 return False, False |
|
515 user = profile.user |
|
516 activated = False |
|
517 if not user.is_active: |
|
518 user.is_active = True |
|
519 user.save() |
|
520 activated = True |
|
521 return (activated, user) |
|
522 |
|
523 def _create_manager(self, email, username, password, first_name, last_name): |
|
524 if username and email and password and len(User.objects.filter(username=username)) == 0: |
|
525 user = User.objects.create(username=username, email=email, first_name=first_name, last_name=last_name, is_active=True) |
|
526 user.set_password(password) |
|
527 user.save() |
|
528 |
|
529 profile = UserProfile.objects.create(user=user) |
|
530 |
|
531 manager = Role.objects.get(name='Manager') |
|
532 UserRole.objects.create(text=None, user=user, role=manager) |
|
533 return user |
|
534 else: |
|
535 return None |
|
536 |
|
537 |
|
538 def create_inactive_user(self, email, send_invitation, **kwargs): |
|
539 #prevent concurrent access |
|
540 cursor = connection.cursor() |
|
541 sql = "LOCK TABLE auth_user IN EXCLUSIVE MODE" |
|
542 cursor.execute(sql) |
|
543 |
|
544 try: |
|
545 user_with_email = User.objects.get(email__iexact=email) |
|
546 except User.DoesNotExist: |
|
547 user = User.objects.create(username=email, email=email) |
|
548 profile = UserProfile.objects.create(user=user) |
|
549 update(user, kwargs) |
|
550 update(profile, kwargs) |
|
551 |
|
552 user.is_active = False |
|
553 user.save() |
|
554 profile.save() |
|
555 |
|
556 note = kwargs.get('note', None) |
|
557 if send_invitation: |
|
558 profile.send_activation_email(note) |
|
559 return user |
|
560 else: |
|
561 return user_with_email |
|
562 |
|
563 |
|
564 from cm.utils.mail import send_mail |
|
565 |
|
566 class UserProfile(KeyModel): |
|
567 modified = models.DateTimeField(auto_now=True) |
|
568 created = models.DateTimeField(auto_now_add=True) |
|
569 |
|
570 user = models.ForeignKey(User, unique=True) |
|
571 |
|
572 allow_contact = models.BooleanField(ugettext_lazy(u'Allow contact'), default=True, help_text=ugettext_lazy(u"Allow email messages from other users")) |
|
573 preferred_language = models.CharField(ugettext_lazy(u'Preferred language'), max_length=2, default="en") |
|
574 is_temp = models.BooleanField(default=False) |
|
575 is_email_error = models.BooleanField(default=False) |
|
576 is_suspended = models.BooleanField(ugettext_lazy(u'Suspended access'), default=False) # used to disable access or to wait for approval when registering |
|
577 |
|
578 objects = RegistrationManager() |
|
579 |
|
580 class Meta: |
|
581 permissions = ( |
|
582 ("can_create_user", "Can create user"), |
|
583 ("can_delete_user", "Can delete user"), |
|
584 ) |
|
585 |
|
586 def __unicode__(self): |
|
587 return unicode(self.user) |
|
588 |
|
589 def global_userrole(self): |
|
590 try: |
|
591 return UserRole.objects.get(user=self.user, text=None) |
|
592 except UserRole.DoesNotExist: |
|
593 return None |
|
594 |
|
595 def global_userrole(self): |
|
596 try: |
|
597 return UserRole.objects.get(user=self.user, text=None) |
|
598 except UserRole.DoesNotExist: |
|
599 return None |
|
600 |
|
601 def admin_print(self): |
|
602 if self.is_suspended: |
|
603 if self.user.is_active: |
|
604 return mark_safe('%s (%s)' % (self.user.username, _(u'suspended'),)) |
|
605 else: |
|
606 return mark_safe('%s (%s)' % (self.user.username, _(u'waiting approval'),)) |
|
607 else: |
|
608 if self.user.is_active: |
|
609 return mark_safe('%s' % self.user.username) |
|
610 else: |
|
611 email_username = self.user.email.split('@')[0] |
|
612 return mark_safe('%s (%s)' % (self.user.username, _(u'pending'),)) |
|
613 |
|
614 def simple_print(self): |
|
615 if self.user.is_active: |
|
616 return self.user.username |
|
617 else: |
|
618 return self.user.email |
|
619 |
|
620 def send_activation_email(self, note=None): |
|
621 self._send_act_invit_email(note=note, template='email/activation_email.txt') |
|
622 |
|
623 def send_invitation_email(self, note=None): |
|
624 self._send_act_invit_email(note=note, template='email/invitation_email.txt') |
|
625 |
|
626 def _send_act_invit_email(self, template, note=None): |
|
627 subject = _(u'Invitation') |
|
628 |
|
629 activate_url = reverse('user-activate', args=[self.adminkey]) |
|
630 message = render_to_string(template, |
|
631 { |
|
632 'activate_url' : activate_url, |
|
633 'note' : note, |
|
634 'CONF': ApplicationConfiguration |
|
635 }) |
|
636 |
|
637 send_mail(subject, message, ApplicationConfiguration['email_from'], [self.user.email]) |
|
638 |
|
639 from django.db.models import signals |
|
640 |
|
641 #def create_profile(sender, **kwargs): |
|
642 # created = kwargs['created'] |
|
643 # if created: |
|
644 # user = kwargs['instance'] |
|
645 # UserProfile.objects.create(user = user) |
|
646 |
|
647 def delete_profile(sender, **kwargs): |
|
648 user_profile = kwargs['instance'] |
|
649 user = user_profile.user |
|
650 user.delete() |
|
651 |
|
652 #signals.post_save.connect(create_profile, sender=User) |
|
653 signals.post_delete.connect(delete_profile, sender=UserProfile) |
|
654 |
|
655 class ActivityManager(models.Manager): |
|
656 pass |
|
657 |
|
658 class Activity(models.Model): |
|
659 created = models.DateTimeField(auto_now_add=True) |
|
660 originator_user = models.ForeignKey(User, related_name='originator_activity', null=True, blank=True, default=None) |
|
661 text = models.ForeignKey(Text, null=True, blank=True, default=None) |
|
662 text_version = models.ForeignKey(TextVersion, null=True, blank=True, default=None) |
|
663 comment = models.ForeignKey(Comment, null=True, blank=True, default=None) |
|
664 user = models.ForeignKey(User, null=True, blank=True, default=None) |
|
665 type = models.CharField(max_length=30) |
|
666 ip = models.IPAddressField(null=True, blank=True, default=None) |
|
667 |
|
668 objects = ActivityManager() |
|
669 |
|
670 # viewable activities (i.e. now 'text-view') |
|
671 VIEWABLE_ACTIVITIES = { |
|
672 'view_comments' : ['comment_created', 'comment_removed'], |
|
673 'view_users' : ['user_created', 'user_activated', 'user_refused', 'user_enabled', 'user_approved', 'user_suspended'], |
|
674 'view_texts' : ['text_created', 'text_removed', 'text_edited', 'text_edited_new_version'], |
|
675 } |
|
676 ACTIVITIES_TYPES = reduce(list.__add__, VIEWABLE_ACTIVITIES.values()) |
|
677 |
|
678 IMGS = { |
|
679 'text_created' : u'page_add_small.png', |
|
680 'text_removed' : u'page_delete_small.png', |
|
681 'text_edited' : u'page_save_small.png', |
|
682 'text_edited_new_version' : u'page_save_small.png', |
|
683 'user_created' : u'user_add_small.png', |
|
684 'user_enabled' : u'user_add_small.png', |
|
685 'user_refused': u'user_delete_small.png', |
|
686 'user_suspended': u'user_delete_small.png', |
|
687 'user_approved': u'user_add_small.png', |
|
688 'user_activated' : u'user_go.png', |
|
689 'comment_created' : u'note_add_small.png', |
|
690 'comment_removed' : u'note_delete_small.png', |
|
691 } |
|
692 |
|
693 #type/msg |
|
694 MSGS = { |
|
695 'text_edited' : _(u'Text %(link_to_text)s edited'), |
|
696 'text_edited_new_version' : _(u'Text %(link_to_text)s edited (new version created)'), |
|
697 'text_created' : _(u'Text %(link_to_text)s added'), |
|
698 'text_removed' : _(u'Text %(link_to_text)s removed'), |
|
699 'comment_created' : _(u'Comment %(link_to_comment)s added on text %(link_to_text)s'), |
|
700 'comment_removed' : _(u'Comment %(link_to_comment)s removed from text %(link_to_text)s'), |
|
701 'user_created' : _(u'User %(username)s added'), |
|
702 'user_enabled' : _(u'User %(username)s access to workspace enabled'), |
|
703 'user_refused' : _(u'User %(username)s access to workspace refused'), |
|
704 'user_suspended' : _(u'User %(username)s access to workspace suspended'), |
|
705 'user_activated' : _(u'User %(username)s access to workspace activated'), |
|
706 'user_approved' : _(u'User %(username)s has activated his account'), |
|
707 } |
|
708 |
|
709 def is_same_user(self, other_activity): |
|
710 if (self.originator_user != None or other_activity.originator_user != None) and self.originator_user != other_activity.originator_user: |
|
711 return False |
|
712 else: |
|
713 return self.ip != None and self.ip == other_activity.ip |
|
714 |
|
715 def linkable_text_title(self, html=True, link=True): |
|
716 # html: whether or not output sould be html |
|
717 format_args = {'link':absolute_reverse('text-view', args=[self.text.key]), 'title':self.text.title} |
|
718 if html and not self.text.deleted : |
|
719 return mark_safe(u'<a href="%(link)s">%(title)s</a>' % format_args) |
|
720 else: |
|
721 if link and not self.text.deleted: |
|
722 return u'%(title)s (%(link)s)' % format_args |
|
723 else: |
|
724 return self.text.title ; |
|
725 |
|
726 def linkable_comment_title(self, html=True, link=True): |
|
727 if self.comment: |
|
728 format_args = {'link':absolute_reverse('text-view-show-comment', args=[self.text.key, self.comment.key]), 'title':self.comment.title} |
|
729 if html and not self.comment.deleted and not self.text.deleted: |
|
730 return mark_safe(u'<a href="%(link)s">%(title)s</a>' % format_args) |
|
731 else : |
|
732 if link and not self.comment.deleted and not self.text.deleted: |
|
733 return u'%(title)s (%(link)s)' % format_args |
|
734 else: |
|
735 return self.comment.title ; |
|
736 else: |
|
737 return u'' |
|
738 |
|
739 def __unicode__(self): |
|
740 return u"%s %s %s %s %s" % (self.type, self.originator_user, self.text, self.comment, self.user) |
|
741 |
|
742 def img_name(self): |
|
743 return self.IMGS.get(self.type) |
|
744 |
|
745 def printable_data_nohtml_link(self): |
|
746 return self.printable_data(html=False, link=True) |
|
747 |
|
748 def printable_data(self, html=True, link=True): |
|
749 msg = self.MSGS.get(self.type, None) |
|
750 if msg: |
|
751 return mark_safe(msg % { |
|
752 'link_to_text' : self.linkable_text_title(html=html, link=link) if self.text else None, |
|
753 'link_to_comment' : self.linkable_comment_title(html=html, link=link) if self.comment else None, |
|
754 'username' : self.user.username if self.user else None, |
|
755 }) |
|
756 return '' |
|
757 |
|
758 def printable_metadata(self, html=True): |
|
759 ret = [] |
|
760 if self.type == 'user_activated': |
|
761 ret.append(_(u'by "%(username)s"') % {'username' : self.originator_user.username}) |
|
762 ret.append(' ') |
|
763 ret.append(_(u"%(time_since)s ago") % {'time_since':timesince(self.created)}) |
|
764 return ''.join(ret) |
|
765 |
|
766 def printable_metadata_absolute(self, html=True): |
|
767 ret = [] |
|
768 if self.type == 'user_activated': |
|
769 ret.append(_(u'by "%(username)s"') % {'username' : self.originator_user.username}) |
|
770 ret.append(u' ') |
|
771 ret.append(datetime_to_user_str(self.created)) |
|
772 return u''.join(ret) |
|
773 |
|
774 import cm.denorm_engine |
|
775 import cm.admin |
|
776 import cm.main |
|
777 import cm.activity |
|
778 import cm.notifications |
|
779 |
|
780 # we fill username with email so we need a bigger value |
|
781 User._meta.get_field('username').max_length = 75 |