Ticket #15098: models.py

File models.py, 18.7 KB (added by tkolar, 13 years ago)

Version of models.py that sets django.contrib.auth.models.SiteProfileNotAvailable.silent_variable_failure=True

Line 
1import datetime
2import urllib
3
4from django.contrib import auth
5from django.contrib.auth.signals import user_logged_in
6from django.core.exceptions import ImproperlyConfigured
7from django.db import models
8from django.db.models.manager import EmptyManager
9from django.contrib.contenttypes.models import ContentType
10from django.utils.encoding import smart_str
11from django.utils.hashcompat import md5_constructor, sha_constructor
12from django.utils.translation import ugettext_lazy as _
13
14
15UNUSABLE_PASSWORD = '!' # This will never be a valid hash
16
17def get_hexdigest(algorithm, salt, raw_password):
18 """
19 Returns a string of the hexdigest of the given plaintext password and salt
20 using the given algorithm ('md5', 'sha1' or 'crypt').
21 """
22 raw_password, salt = smart_str(raw_password), smart_str(salt)
23 if algorithm == 'crypt':
24 try:
25 import crypt
26 except ImportError:
27 raise ValueError('"crypt" password algorithm not supported in this environment')
28 return crypt.crypt(raw_password, salt)
29
30 if algorithm == 'md5':
31 return md5_constructor(salt + raw_password).hexdigest()
32 elif algorithm == 'sha1':
33 return sha_constructor(salt + raw_password).hexdigest()
34 raise ValueError("Got unknown password algorithm type in password.")
35
36def check_password(raw_password, enc_password):
37 """
38 Returns a boolean of whether the raw_password was correct. Handles
39 encryption formats behind the scenes.
40 """
41 algo, salt, hsh = enc_password.split('$')
42 return hsh == get_hexdigest(algo, salt, raw_password)
43
44def update_last_login(sender, user, **kwargs):
45 """
46 A signal receiver which updates the last_login date for
47 the user logging in.
48 """
49 user.last_login = datetime.datetime.now()
50 user.save()
51user_logged_in.connect(update_last_login)
52
53class SiteProfileNotAvailable(Exception):
54 silent_variable_failure=True
55
56class PermissionManager(models.Manager):
57 def get_by_natural_key(self, codename, app_label, model):
58 return self.get(
59 codename=codename,
60 content_type=ContentType.objects.get_by_natural_key(app_label, model)
61 )
62
63class Permission(models.Model):
64 """The permissions system provides a way to assign permissions to specific users and groups of users.
65
66 The permission system is used by the Django admin site, but may also be useful in your own code. The Django admin site uses permissions as follows:
67
68 - The "add" permission limits the user's ability to view the "add" form and add an object.
69 - The "change" permission limits a user's ability to view the change list, view the "change" form and change an object.
70 - The "delete" permission limits the ability to delete an object.
71
72 Permissions are set globally per type of object, not per specific object instance. It is possible to say "Mary may change news stories," but it's not currently possible to say "Mary may change news stories, but only the ones she created herself" or "Mary may only change news stories that have a certain status or publication date."
73
74 Three basic permissions -- add, change and delete -- are automatically created for each Django model.
75 """
76 name = models.CharField(_('name'), max_length=50)
77 content_type = models.ForeignKey(ContentType)
78 codename = models.CharField(_('codename'), max_length=100)
79 objects = PermissionManager()
80
81 class Meta:
82 verbose_name = _('permission')
83 verbose_name_plural = _('permissions')
84 unique_together = (('content_type', 'codename'),)
85 ordering = ('content_type__app_label', 'content_type__model', 'codename')
86
87 def __unicode__(self):
88 return u"%s | %s | %s" % (
89 unicode(self.content_type.app_label),
90 unicode(self.content_type),
91 unicode(self.name))
92
93 def natural_key(self):
94 return (self.codename,) + self.content_type.natural_key()
95 natural_key.dependencies = ['contenttypes.contenttype']
96
97class Group(models.Model):
98 """Groups are a generic way of categorizing users to apply permissions, or some other label, to those users. A user can belong to any number of groups.
99
100 A user in a group automatically has all the permissions granted to that group. For example, if the group Site editors has the permission can_edit_home_page, any user in that group will have that permission.
101
102 Beyond permissions, groups are a convenient way to categorize users to apply some label, or extended functionality, to them. For example, you could create a group 'Special users', and you could write code that would do special things to those users -- such as giving them access to a members-only portion of your site, or sending them members-only e-mail messages.
103 """
104 name = models.CharField(_('name'), max_length=80, unique=True)
105 permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True)
106
107 class Meta:
108 verbose_name = _('group')
109 verbose_name_plural = _('groups')
110
111 def __unicode__(self):
112 return self.name
113
114class UserManager(models.Manager):
115 def create_user(self, username, email, password=None):
116 """
117 Creates and saves a User with the given username, e-mail and password.
118 """
119 now = datetime.datetime.now()
120
121 # Normalize the address by lowercasing the domain part of the email
122 # address.
123 try:
124 email_name, domain_part = email.strip().split('@', 1)
125 except ValueError:
126 pass
127 else:
128 email = '@'.join([email_name, domain_part.lower()])
129
130 user = self.model(username=username, email=email, is_staff=False,
131 is_active=True, is_superuser=False, last_login=now,
132 date_joined=now)
133
134 user.set_password(password)
135 user.save(using=self._db)
136 return user
137
138 def create_superuser(self, username, email, password):
139 u = self.create_user(username, email, password)
140 u.is_staff = True
141 u.is_active = True
142 u.is_superuser = True
143 u.save(using=self._db)
144 return u
145
146 def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
147 "Generates a random password with the given length and given allowed_chars"
148 # Note that default value of allowed_chars does not have "I" or letters
149 # that look like it -- just to avoid confusion.
150 from random import choice
151 return ''.join([choice(allowed_chars) for i in range(length)])
152
153
154# A few helper functions for common logic between User and AnonymousUser.
155def _user_get_all_permissions(user, obj):
156 permissions = set()
157 anon = user.is_anonymous()
158 for backend in auth.get_backends():
159 if not anon or backend.supports_anonymous_user:
160 if hasattr(backend, "get_all_permissions"):
161 if obj is not None:
162 if backend.supports_object_permissions:
163 permissions.update(
164 backend.get_all_permissions(user, obj)
165 )
166 else:
167 permissions.update(backend.get_all_permissions(user))
168 return permissions
169
170
171def _user_has_perm(user, perm, obj):
172 anon = user.is_anonymous()
173 active = user.is_active
174 for backend in auth.get_backends():
175 if (not active and not anon and backend.supports_inactive_user) or \
176 (not anon or backend.supports_anonymous_user):
177 if hasattr(backend, "has_perm"):
178 if obj is not None:
179 if (backend.supports_object_permissions and
180 backend.has_perm(user, perm, obj)):
181 return True
182 else:
183 if backend.has_perm(user, perm):
184 return True
185 return False
186
187
188def _user_has_module_perms(user, app_label):
189 anon = user.is_anonymous()
190 active = user.is_active
191 for backend in auth.get_backends():
192 if (not active and not anon and backend.supports_inactive_user) or \
193 (not anon or backend.supports_anonymous_user):
194 if hasattr(backend, "has_module_perms"):
195 if backend.has_module_perms(user, app_label):
196 return True
197 return False
198
199
200class User(models.Model):
201 """
202 Users within the Django authentication system are represented by this model.
203
204 Username and password are required. Other fields are optional.
205 """
206 username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
207 first_name = models.CharField(_('first name'), max_length=30, blank=True)
208 last_name = models.CharField(_('last name'), max_length=30, blank=True)
209 email = models.EmailField(_('e-mail address'), blank=True)
210 password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
211 is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site."))
212 is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
213 is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them."))
214 last_login = models.DateTimeField(_('last login'), default=datetime.datetime.now)
215 date_joined = models.DateTimeField(_('date joined'), default=datetime.datetime.now)
216 groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True,
217 help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in."))
218 user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True)
219 objects = UserManager()
220
221 class Meta:
222 verbose_name = _('user')
223 verbose_name_plural = _('users')
224
225 def __unicode__(self):
226 return self.username
227
228 def get_absolute_url(self):
229 return "/users/%s/" % urllib.quote(smart_str(self.username))
230
231 def is_anonymous(self):
232 """
233 Always returns False. This is a way of comparing User objects to
234 anonymous users.
235 """
236 return False
237
238 def is_authenticated(self):
239 """
240 Always return True. This is a way to tell if the user has been
241 authenticated in templates.
242 """
243 return True
244
245 def get_full_name(self):
246 "Returns the first_name plus the last_name, with a space in between."
247 full_name = u'%s %s' % (self.first_name, self.last_name)
248 return full_name.strip()
249
250 def set_password(self, raw_password):
251 if raw_password is None:
252 self.set_unusable_password()
253 else:
254 import random
255 algo = 'sha1'
256 salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
257 hsh = get_hexdigest(algo, salt, raw_password)
258 self.password = '%s$%s$%s' % (algo, salt, hsh)
259
260 def check_password(self, raw_password):
261 """
262 Returns a boolean of whether the raw_password was correct. Handles
263 encryption formats behind the scenes.
264 """
265 # Backwards-compatibility check. Older passwords won't include the
266 # algorithm or salt.
267 if '$' not in self.password:
268 is_correct = (self.password == get_hexdigest('md5', '', raw_password))
269 if is_correct:
270 # Convert the password to the new, more secure format.
271 self.set_password(raw_password)
272 self.save()
273 return is_correct
274 return check_password(raw_password, self.password)
275
276 def set_unusable_password(self):
277 # Sets a value that will never be a valid hash
278 self.password = UNUSABLE_PASSWORD
279
280 def has_usable_password(self):
281 if self.password is None \
282 or self.password == UNUSABLE_PASSWORD:
283 return False
284 else:
285 return True
286
287 def get_group_permissions(self, obj=None):
288 """
289 Returns a list of permission strings that this user has through
290 his/her groups. This method queries all available auth backends.
291 If an object is passed in, only permissions matching this object
292 are returned.
293 """
294 permissions = set()
295 for backend in auth.get_backends():
296 if hasattr(backend, "get_group_permissions"):
297 if obj is not None:
298 if backend.supports_object_permissions:
299 permissions.update(
300 backend.get_group_permissions(self, obj)
301 )
302 else:
303 permissions.update(backend.get_group_permissions(self))
304 return permissions
305
306 def get_all_permissions(self, obj=None):
307 return _user_get_all_permissions(self, obj)
308
309 def has_perm(self, perm, obj=None):
310 """
311 Returns True if the user has the specified permission. This method
312 queries all available auth backends, but returns immediately if any
313 backend returns True. Thus, a user who has permission from a single
314 auth backend is assumed to have permission in general. If an object
315 is provided, permissions for this specific object are checked.
316 """
317
318 # Active superusers have all permissions.
319 if self.is_active and self.is_superuser:
320 return True
321
322 # Otherwise we need to check the backends.
323 return _user_has_perm(self, perm, obj)
324
325 def has_perms(self, perm_list, obj=None):
326 """
327 Returns True if the user has each of the specified permissions.
328 If object is passed, it checks if the user has all required perms
329 for this object.
330 """
331 for perm in perm_list:
332 if not self.has_perm(perm, obj):
333 return False
334 return True
335
336 def has_module_perms(self, app_label):
337 """
338 Returns True if the user has any permissions in the given app
339 label. Uses pretty much the same logic as has_perm, above.
340 """
341 # Active superusers have all permissions.
342 if self.is_active and self.is_superuser:
343 return True
344
345 return _user_has_module_perms(self, app_label)
346
347 def get_and_delete_messages(self):
348 messages = []
349 for m in self.message_set.all():
350 messages.append(m.message)
351 m.delete()
352 return messages
353
354 def email_user(self, subject, message, from_email=None):
355 "Sends an e-mail to this User."
356 from django.core.mail import send_mail
357 send_mail(subject, message, from_email, [self.email])
358
359 def get_profile(self):
360 """
361 Returns site-specific profile for this user. Raises
362 SiteProfileNotAvailable if this site does not allow profiles.
363 """
364 if not hasattr(self, '_profile_cache'):
365 from django.conf import settings
366 if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
367 raise SiteProfileNotAvailable('You need to set AUTH_PROFILE_MO'
368 'DULE in your project settings')
369 try:
370 app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
371 except ValueError:
372 raise SiteProfileNotAvailable('app_label and model_name should'
373 ' be separated by a dot in the AUTH_PROFILE_MODULE set'
374 'ting')
375
376 try:
377 model = models.get_model(app_label, model_name)
378 if model is None:
379 raise SiteProfileNotAvailable('Unable to load the profile '
380 'model, check AUTH_PROFILE_MODULE in your project sett'
381 'ings')
382 self._profile_cache = model._default_manager.using(self._state.db).get(user__id__exact=self.id)
383 self._profile_cache.user = self
384 except (ImportError, ImproperlyConfigured):
385 raise SiteProfileNotAvailable
386 return self._profile_cache
387
388 def _get_message_set(self):
389 import warnings
390 warnings.warn('The user messaging API is deprecated. Please update'
391 ' your code to use the new messages framework.',
392 category=DeprecationWarning)
393 return self._message_set
394 message_set = property(_get_message_set)
395
396class Message(models.Model):
397 """
398 The message system is a lightweight way to queue messages for given
399 users. A message is associated with a User instance (so it is only
400 applicable for registered users). There's no concept of expiration or
401 timestamps. Messages are created by the Django admin after successful
402 actions. For example, "The poll Foo was created successfully." is a
403 message.
404 """
405 user = models.ForeignKey(User, related_name='_message_set')
406 message = models.TextField(_('message'))
407
408 def __unicode__(self):
409 return self.message
410
411class AnonymousUser(object):
412 id = None
413 username = ''
414 is_staff = False
415 is_active = False
416 is_superuser = False
417 _groups = EmptyManager()
418 _user_permissions = EmptyManager()
419
420 def __init__(self):
421 pass
422
423 def __unicode__(self):
424 return 'AnonymousUser'
425
426 def __str__(self):
427 return unicode(self).encode('utf-8')
428
429 def __eq__(self, other):
430 return isinstance(other, self.__class__)
431
432 def __ne__(self, other):
433 return not self.__eq__(other)
434
435 def __hash__(self):
436 return 1 # instances always return the same hash value
437
438 def save(self):
439 raise NotImplementedError
440
441 def delete(self):
442 raise NotImplementedError
443
444 def set_password(self, raw_password):
445 raise NotImplementedError
446
447 def check_password(self, raw_password):
448 raise NotImplementedError
449
450 def _get_groups(self):
451 return self._groups
452 groups = property(_get_groups)
453
454 def _get_user_permissions(self):
455 return self._user_permissions
456 user_permissions = property(_get_user_permissions)
457
458 def get_group_permissions(self, obj=None):
459 return set()
460
461 def get_all_permissions(self, obj=None):
462 return _user_get_all_permissions(self, obj=obj)
463
464 def has_perm(self, perm, obj=None):
465 return _user_has_perm(self, perm, obj=obj)
466
467 def has_perms(self, perm_list, obj=None):
468 for perm in perm_list:
469 if not self.has_perm(perm, obj):
470 return False
471 return True
472
473 def has_module_perms(self, module):
474 return _user_has_module_perms(self, module)
475
476 def get_and_delete_messages(self):
477 return []
478
479 def is_anonymous(self):
480 return True
481
482 def is_authenticated(self):
483 return False
Back to Top