7fdde19c379478344fcfba5229f14a7023c6ea67
[plstackapi.git] / planetstack / core / models / user.py
1 import os
2 import datetime
3 import sys
4 import hashlib
5 from collections import defaultdict
6 from django.forms.models import model_to_dict
7 from django.db import models
8 from django.db.models import F, Q
9 from core.models import PlCoreBase,Site, DashboardView, DiffModelMixIn
10 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
11 from timezones.fields import TimeZoneField
12 from operator import itemgetter, attrgetter
13 from django.core.mail import EmailMultiAlternatives
14 from core.middleware import get_request
15 import model_policy
16 from django.core.exceptions import PermissionDenied
17
18 # ------ from plcorebase.py ------
19 try:
20     # This is a no-op if observer_disabled is set to 1 in the config file
21     from observer import *
22 except:
23     print >> sys.stderr, "import of observer failed! printing traceback and disabling observer:"
24     import traceback
25     traceback.print_exc()
26
27     # guard against something failing
28     def notify_observer(*args, **kwargs):
29         pass
30 # ------ ------
31
32 # Create your models here.
33 class UserManager(BaseUserManager):
34     def create_user(self, email, firstname, lastname, password=None):
35         """
36         Creates and saves a User with the given email, date of
37         birth and password.
38         """
39         if not email:
40             raise ValueError('Users must have an email address')
41
42         user = self.model(
43             email=UserManager.normalize_email(email),
44             firstname=firstname,
45             lastname=lastname,
46             password=password
47         )
48         #user.set_password(password)
49         user.is_admin = True
50         user.save(using=self._db)
51         return user
52
53     def create_superuser(self, email, firstname, lastname, password):
54         """
55         Creates and saves a superuser with the given email, date of
56         birth and password.
57         """
58         user = self.create_user(email,
59             password=password,
60             firstname=firstname,
61             lastname=lastname
62         )
63         user.is_admin = True
64         user.save(using=self._db)
65         return user
66
67     def get_queryset(self):
68         parent=super(UserManager, self)
69         if hasattr(parent, "get_queryset"):
70             return parent.get_queryset().filter(deleted=False)
71         else:
72             return parent.get_query_set().filter(deleted=False)
73
74     # deprecated in django 1.7 in favor of get_queryset().
75     def get_query_set(self):
76         return self.get_queryset()
77
78 class DeletedUserManager(UserManager):
79     def get_queryset(self):
80         return super(UserManager, self).get_query_set().filter(deleted=True)
81
82     # deprecated in django 1.7 in favor of get_queryset()
83     def get_query_set(self):
84         return self.get_queryset()
85
86 class User(AbstractBaseUser): #, DiffModelMixIn):
87
88     # ---- copy stuff from DiffModelMixin ----
89
90     @property
91     def _dict(self):
92         return model_to_dict(self, fields=[field.name for field in
93                              self._meta.fields])
94
95     @property
96     def diff(self):
97         d1 = self._initial
98         d2 = self._dict
99         diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
100         return dict(diffs)
101
102     @property
103     def has_changed(self):
104         return bool(self.diff)
105
106     @property
107     def changed_fields(self):
108         return self.diff.keys()
109
110     def has_field_changed(self, field_name):
111         return field_name in self.diff.keys()
112
113     def get_field_diff(self, field_name):
114         return self.diff.get(field_name, None)
115
116     #classmethod
117     def getValidators(cls):
118         """ primarily for REST API, return a dictionary of field names mapped
119             to lists of the type of validations that need to be applied to
120             those fields.
121         """
122         validators = {}
123         for field in cls._meta.fields:
124             l = []
125             if field.blank==False:
126                 l.append("notBlank")
127             if field.__class__.__name__=="URLField":
128                 l.append("url")
129             validators[field.name] = l
130         return validators
131     # ---- end copy stuff from DiffModelMixin ----
132
133     @property
134     def remote_password(self):
135         return hashlib.md5(self.password).hexdigest()[:12]
136
137     class Meta:
138         app_label = "core"
139
140     email = models.EmailField(
141         verbose_name='email address',
142         max_length=255,
143         unique=True,
144         db_index=True,
145     )
146
147     username = models.CharField(max_length=255, default="Something" )
148
149     firstname = models.CharField(help_text="person's given name", max_length=200)
150     lastname = models.CharField(help_text="person's surname", max_length=200)
151
152     phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100)
153     user_url = models.URLField(null=True, blank=True)
154     site = models.ForeignKey(Site, related_name='users', help_text="Site this user will be homed too", null=True)
155     public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
156
157     is_active = models.BooleanField(default=True)
158     is_admin = models.BooleanField(default=True)
159     is_staff = models.BooleanField(default=True)
160     is_readonly = models.BooleanField(default=False)
161
162     created = models.DateTimeField(auto_now_add=True)
163     updated = models.DateTimeField(auto_now=True)
164     enacted = models.DateTimeField(null=True, default=None)
165     backend_status = models.CharField(max_length=140,
166                                       default="Provisioning in progress")
167     deleted = models.BooleanField(default=False)
168
169     timezone = TimeZoneField()
170
171     dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
172
173     objects = UserManager()
174     deleted_objects = DeletedUserManager()
175
176     USERNAME_FIELD = 'email'
177     REQUIRED_FIELDS = ['firstname', 'lastname']
178
179     PI_FORBIDDEN_FIELDS = ["is_admin", "site", "is_staff"]
180     USER_FORBIDDEN_FIELDS = ["is_admin", "is_active", "site", "is_staff", "is_readonly"]
181
182     def __init__(self, *args, **kwargs):
183         super(User, self).__init__(*args, **kwargs)
184         self._initial = self._dict # for DiffModelMixIn
185
186     def isReadOnlyUser(self):
187         return self.is_readonly
188
189     def get_full_name(self):
190         # The user is identified by their email address
191         return self.email
192
193     def get_short_name(self):
194         # The user is identified by their email address
195         return self.email
196
197     def delete(self, *args, **kwds):
198         # so we have something to give the observer
199         purge = kwds.get('purge',False)
200         if purge:
201             del kwds['purge']
202         try:
203             purge = purge or observer_disabled
204         except NameError:
205             pass
206             
207         if (purge):
208             super(User, self).delete(*args, **kwds)
209         else:
210             self.deleted = True
211             self.enacted=None
212             self.save(update_fields=['enacted','deleted'])
213
214     @property
215     def keyname(self):
216         return self.email[:self.email.find('@')]
217
218     def __unicode__(self):
219         return self.email
220
221     def has_perm(self, perm, obj=None):
222         "Does the user have a specific permission?"
223         # Simplest possible answer: Yes, always
224         return True
225
226     def has_module_perms(self, app_label):
227         "Does the user have permissions to view the app `app_label`?"
228         # Simplest possible answer: Yes, always
229         return True
230
231     def is_superuser(self):
232         return False
233
234     def get_dashboards(self):
235         DEFAULT_DASHBOARDS=["Tenant"]
236
237         dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
238         dashboards = [x.dashboardView for x in dashboards]
239
240         if not dashboards:
241             for dashboardName in DEFAULT_DASHBOARDS:
242                 dbv = DashboardView.objects.filter(name=dashboardName)
243                 if dbv:
244                     dashboards.append(dbv[0])
245
246         return dashboards
247
248 #    def get_roles(self):
249 #        from core.models.site import SitePrivilege
250 #        from core.models.slice import SliceMembership
251 #
252 #        site_privileges = SitePrivilege.objects.filter(user=self)
253 #        slice_memberships = SliceMembership.objects.filter(user=self)
254 #        roles = defaultdict(list)
255 #        for site_privilege in site_privileges:
256 #            roles[site_privilege.role.role_type].append(site_privilege.site.login_base)
257 #        for slice_membership in slice_memberships:
258 #            roles[slice_membership.role.role_type].append(slice_membership.slice.name)
259 #        return roles   
260
261     def save(self, *args, **kwds):
262         if not self.id:
263             self.set_password(self.password)
264         if self.is_active:
265             if self.password=="!":\r
266                 self.send_temporary_password()\r
267 \r
268         self.username = self.email
269         super(User, self).save(*args, **kwds)
270
271         self._initial = self._dict
272
273     def send_temporary_password(self):
274         password = User.objects.make_random_password()
275         self.set_password(password)\r
276         subject, from_email, to = 'OpenCloud Account Credentials', 'support@opencloud.us', str(self.email)\r
277         text_content = 'This is an important message.'\r
278         userUrl="http://%s/" % get_request().get_host()\r
279         html_content = """<p>Your account has been created on OpenCloud. Please log in <a href="""+userUrl+""">here</a> to activate your account<br><br>Username: """+self.email+"""<br>Temporary Password: """+password+"""<br>Please change your password once you successully login into the site.</p>"""\r
280         msg = EmailMultiAlternatives(subject,text_content, from_email, [to])\r
281         msg.attach_alternative(html_content, "text/html")\r
282         msg.send()
283
284     def can_update(self, user):
285         from core.models import SitePrivilege
286         _cant_update_fieldName = None
287         if user.is_readonly:
288             return False
289         if user.is_admin:
290             return True
291         # site pis can update
292         site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
293         for site_priv in site_privs:
294             if site_priv.role.role == 'pi':
295                 for fieldName in self.diff.keys():
296                     if fieldName in self.PI_FORBIDDEN_FIELDS:
297                         _cant_update_fieldName = fieldName
298                         return False
299                 return True
300         if (user.id == self.id):
301             for fieldName in self.diff.keys():
302                 if fieldName in self.USER_FORBIDDEN_FIELDS:
303                     _cant_update_fieldName = fieldName
304                     return False
305             return True
306
307         return False
308
309     @staticmethod
310     def select_by_user(user):
311         if user.is_admin:
312             qs = User.objects.all()
313         else:
314             # can see all users at any site where this user has pi role
315             from core.models.site import SitePrivilege
316             site_privs = SitePrivilege.objects.filter(user=user)
317             sites = [sp.site for sp in site_privs if sp.role.role == 'pi']
318             # get site privs of users at these sites
319             site_privs = SitePrivilege.objects.filter(site__in=sites)
320             user_ids = [sp.user.id for sp in site_privs] + [user.id]
321             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
322         return qs
323
324     def save_by_user(self, user, *args, **kwds):
325         if not self.can_update(user):
326             if getattr(self, "_cant_update_fieldName", None) is not None:
327                 raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
328             else:
329                 raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
330
331         self.save(*args, **kwds)
332
333     def delete_by_user(self, user, *args, **kwds):
334         if not self.can_update(user):
335             raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
336         self.delete(*args, **kwds)
337
338 class UserDashboardView(PlCoreBase):
339      user = models.ForeignKey(User, related_name='userdashboardviews')
340      dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
341      order = models.IntegerField(default=0)