Added timestamp for model policy scheduling to the User class
[plstackapi.git] / planetstack / core / models / user.py
index 3c4d377..244ef6a 100644 (file)
@@ -1,13 +1,33 @@
 import os
 import datetime
 import os
 import datetime
+import sys
+import hashlib
 from collections import defaultdict
 from collections import defaultdict
+from django.forms.models import model_to_dict
 from django.db import models
 from django.db.models import F, Q
 from django.db import models
 from django.db.models import F, Q
-from core.models import PlCoreBase,Site, DashboardView
-from core.models.site import Deployment
+from core.models import PlCoreBase,Site, DashboardView, DiffModelMixIn
 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
 from timezones.fields import TimeZoneField
 from operator import itemgetter, attrgetter
 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
 from timezones.fields import TimeZoneField
 from operator import itemgetter, attrgetter
+from django.core.mail import EmailMultiAlternatives
+from core.middleware import get_request
+import model_policy
+from django.core.exceptions import PermissionDenied
+
+# ------ from plcorebase.py ------
+try:
+    # This is a no-op if observer_disabled is set to 1 in the config file
+    from observer import *
+except:
+    print >> sys.stderr, "import of observer failed! printing traceback and disabling observer:"
+    import traceback
+    traceback.print_exc()
+
+    # guard against something failing
+    def notify_observer(*args, **kwargs):
+        pass
+# ------ ------
 
 # Create your models here.
 class UserManager(BaseUserManager):
 
 # Create your models here.
 class UserManager(BaseUserManager):
@@ -44,8 +64,75 @@ class UserManager(BaseUserManager):
         user.save(using=self._db)
         return user
 
         user.save(using=self._db)
         return user
 
+    def get_queryset(self):
+        parent=super(UserManager, self)
+        if hasattr(parent, "get_queryset"):
+            return parent.get_queryset().filter(deleted=False)
+        else:
+            return parent.get_query_set().filter(deleted=False)
+
+    # deprecated in django 1.7 in favor of get_queryset().
+    def get_query_set(self):
+        return self.get_queryset()
+
+class DeletedUserManager(UserManager):
+    def get_queryset(self):
+        return super(UserManager, self).get_query_set().filter(deleted=True)
+
+    # deprecated in django 1.7 in favor of get_queryset()
+    def get_query_set(self):
+        return self.get_queryset()
+
+class User(AbstractBaseUser): #, DiffModelMixIn):
+
+    # ---- copy stuff from DiffModelMixin ----
+
+    @property
+    def _dict(self):
+        return model_to_dict(self, fields=[field.name for field in
+                             self._meta.fields])
+
+    @property
+    def diff(self):
+        d1 = self._initial
+        d2 = self._dict
+        diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]]
+        return dict(diffs)
+
+    @property
+    def has_changed(self):
+        return bool(self.diff)
+
+    @property
+    def changed_fields(self):
+        return self.diff.keys()
+
+    def has_field_changed(self, field_name):
+        return field_name in self.diff.keys()
 
 
-class User(AbstractBaseUser):
+    def get_field_diff(self, field_name):
+        return self.diff.get(field_name, None)
+
+    #classmethod
+    def getValidators(cls):
+        """ primarily for REST API, return a dictionary of field names mapped
+            to lists of the type of validations that need to be applied to
+            those fields.
+        """
+        validators = {}
+        for field in cls._meta.fields:
+            l = []
+            if field.blank==False:
+                l.append("notBlank")
+            if field.__class__.__name__=="URLField":
+                l.append("url")
+            validators[field.name] = l
+        return validators
+    # ---- end copy stuff from DiffModelMixin ----
+
+    @property
+    def remote_password(self):
+        return hashlib.md5(self.password).hexdigest()[:12]
 
     class Meta:
         app_label = "core"
 
     class Meta:
         app_label = "core"
@@ -75,16 +162,28 @@ class User(AbstractBaseUser):
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     enacted = models.DateTimeField(null=True, default=None)
     created = models.DateTimeField(auto_now_add=True)
     updated = models.DateTimeField(auto_now=True)
     enacted = models.DateTimeField(null=True, default=None)
+    policed = models.DateTimeField(null=True, default=None)
+    backend_status = models.CharField(max_length=140,
+                                      default="Provisioning in progress")
+    deleted = models.BooleanField(default=False)
 
     timezone = TimeZoneField()
 
     dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
 
     objects = UserManager()
 
     timezone = TimeZoneField()
 
     dashboards = models.ManyToManyField('DashboardView', through='UserDashboardView', blank=True)
 
     objects = UserManager()
+    deleted_objects = DeletedUserManager()
 
     USERNAME_FIELD = 'email'
     REQUIRED_FIELDS = ['firstname', 'lastname']
 
 
     USERNAME_FIELD = 'email'
     REQUIRED_FIELDS = ['firstname', 'lastname']
 
+    PI_FORBIDDEN_FIELDS = ["is_admin", "site", "is_staff"]
+    USER_FORBIDDEN_FIELDS = ["is_admin", "is_active", "site", "is_staff", "is_readonly"]
+
+    def __init__(self, *args, **kwargs):
+        super(User, self).__init__(*args, **kwargs)
+        self._initial = self._dict # for DiffModelMixIn
+
     def isReadOnlyUser(self):
         return self.is_readonly
 
     def isReadOnlyUser(self):
         return self.is_readonly
 
@@ -96,6 +195,23 @@ class User(AbstractBaseUser):
         # The user is identified by their email address
         return self.email
 
         # The user is identified by their email address
         return self.email
 
+    def delete(self, *args, **kwds):
+        # so we have something to give the observer
+        purge = kwds.get('purge',False)
+        if purge:
+            del kwds['purge']
+        try:
+            purge = purge or observer_disabled
+        except NameError:
+            pass
+            
+        if (purge):
+            super(User, self).delete(*args, **kwds)
+        else:
+            self.deleted = True
+            self.enacted=None
+            self.save(update_fields=['enacted','deleted'])
+
     @property
     def keyname(self):
         return self.email[:self.email.find('@')]
     @property
     def keyname(self):
         return self.email[:self.email.find('@')]
@@ -119,7 +235,7 @@ class User(AbstractBaseUser):
     def get_dashboards(self):
         DEFAULT_DASHBOARDS=["Tenant"]
 
     def get_dashboards(self):
         DEFAULT_DASHBOARDS=["Tenant"]
 
-        dashboards = sorted(list(self.dashboardViews.all()), key=attrgetter('order'))
+        dashboards = sorted(list(self.userdashboardviews.all()), key=attrgetter('order'))
         dashboards = [x.dashboardView for x in dashboards]
 
         if not dashboards:
         dashboards = [x.dashboardView for x in dashboards]
 
         if not dashboards:
@@ -145,9 +261,51 @@ class User(AbstractBaseUser):
 
     def save(self, *args, **kwds):
         if not self.id:
 
     def save(self, *args, **kwds):
         if not self.id:
-            self.set_password(self.password)    
+            self.set_password(self.password)
+        if self.is_active:
+            if self.password=="!":\r
+                self.send_temporary_password()\r
+\r
         self.username = self.email
         self.username = self.email
-        super(User, self).save(*args, **kwds)  
+        super(User, self).save(*args, **kwds)
+
+        self._initial = self._dict
+
+    def send_temporary_password(self):
+        password = User.objects.make_random_password()
+        self.set_password(password)\r
+        subject, from_email, to = 'OpenCloud Account Credentials', 'support@opencloud.us', str(self.email)\r
+        text_content = 'This is an important message.'\r
+        userUrl="http://%s/" % get_request().get_host()\r
+        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
+        msg = EmailMultiAlternatives(subject,text_content, from_email, [to])\r
+        msg.attach_alternative(html_content, "text/html")\r
+        msg.send()
+
+    def can_update(self, user):
+        from core.models import SitePrivilege
+        _cant_update_fieldName = None
+        if user.is_readonly:
+            return False
+        if user.is_admin:
+            return True
+        # site pis can update
+        site_privs = SitePrivilege.objects.filter(user=user, site=self.site)
+        for site_priv in site_privs:
+            if site_priv.role.role == 'pi':
+                for fieldName in self.diff.keys():
+                    if fieldName in self.PI_FORBIDDEN_FIELDS:
+                        _cant_update_fieldName = fieldName
+                        return False
+                return True
+        if (user.id == self.id):
+            for fieldName in self.diff.keys():
+                if fieldName in self.USER_FORBIDDEN_FIELDS:
+                    _cant_update_fieldName = fieldName
+                    return False
+            return True
+
+        return False
 
     @staticmethod
     def select_by_user(user):
 
     @staticmethod
     def select_by_user(user):
@@ -160,11 +318,25 @@ class User(AbstractBaseUser):
             sites = [sp.site for sp in site_privs if sp.role.role == 'pi']
             # get site privs of users at these sites
             site_privs = SitePrivilege.objects.filter(site__in=sites)
             sites = [sp.site for sp in site_privs if sp.role.role == 'pi']
             # get site privs of users at these sites
             site_privs = SitePrivilege.objects.filter(site__in=sites)
-            user_ids = [sp.user.id for sp in site_privs] + [user.id] 
+            user_ids = [sp.user.id for sp in site_privs] + [user.id]
             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
             qs = User.objects.filter(Q(site__in=sites) | Q(id__in=user_ids))
-        return qs            
+        return qs
+
+    def save_by_user(self, user, *args, **kwds):
+        if not self.can_update(user):
+            if getattr(self, "_cant_update_fieldName", None) is not None:
+                raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
+            else:
+                raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
+
+        self.save(*args, **kwds)
+
+    def delete_by_user(self, user, *args, **kwds):
+        if not self.can_update(user):
+            raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
+        self.delete(*args, **kwds)
 
 class UserDashboardView(PlCoreBase):
 
 class UserDashboardView(PlCoreBase):
-     user = models.ForeignKey(User, related_name="dashboardViews")
-     dashboardView = models.ForeignKey(DashboardView, related_name="dashboardViews")
+     user = models.ForeignKey(User, related_name='userdashboardviews')
+     dashboardView = models.ForeignKey(DashboardView, related_name='userdashboardviews')
      order = models.IntegerField(default=0)
      order = models.IntegerField(default=0)