blob: e610d1b3b7b63166546bfda6015cf8c449ffb7ad [file] [log] [blame]
Scott Baker55b786a2015-02-02 14:28:35 -08001import datetime
Siobhan Tully4bc09f22013-04-10 21:15:21 -04002import os
Scott Baker65d5a9a2014-05-26 15:58:09 -07003import sys
Siobhan Tully4bc09f22013-04-10 21:15:21 -04004from django.db import models
Scott Baker13acdd62013-05-08 17:42:56 -07005from django.forms.models import model_to_dict
Scott Bakerc1c45f82014-01-21 16:23:51 -08006from django.core.urlresolvers import reverse
Scott Baker6ecd4262014-01-21 23:15:21 -08007from django.forms.models import model_to_dict
Scott Baker02b59522014-09-17 22:18:46 -07008from django.utils import timezone
Scott Bakere5f41b02014-10-02 22:50:18 -07009from django.core.exceptions import PermissionDenied
Sapan Bhatia39097192014-09-04 00:39:19 -040010import model_policy
Sapan Bhatiaf6613e32014-11-12 10:38:23 -050011from model_autodeletion import ephemeral_models
Scott Bakere5f9d7d2015-02-10 18:24:20 -080012from cgi import escape as html_escape
Scott Baker9e990742014-03-19 22:14:58 -070013
14try:
15 # This is a no-op if observer_disabled is set to 1 in the config file
16 from observer import *
17except:
Scott Baker65d5a9a2014-05-26 15:58:09 -070018 print >> sys.stderr, "import of observer failed! printing traceback and disabling observer:"
Scott Baker9e990742014-03-19 22:14:58 -070019 import traceback
20 traceback.print_exc()
21
22 # guard against something failing
Scott Bakerfd44dfc2014-05-23 13:20:53 -070023 def notify_observer(*args, **kwargs):
Scott Baker9e990742014-03-19 22:14:58 -070024 pass
Siobhan Tully4bc09f22013-04-10 21:15:21 -040025
Sapan Bhatia3089d832014-04-29 14:36:51 -040026# This manager will be inherited by all subclasses because
27# the core model is abstract.
Sapan Bhatia15bf5ac2014-07-21 20:06:59 -040028class PlCoreBaseDeletionManager(models.Manager):
Scott Baker92f14222014-09-12 12:57:27 -070029 def get_queryset(self):
Scott Baker02b59522014-09-17 22:18:46 -070030 parent=super(PlCoreBaseDeletionManager, self)
31 if hasattr(parent, "get_queryset"):
32 return parent.get_queryset().filter(deleted=True)
33 else:
34 return parent.get_query_set().filter(deleted=True)
Sapan Bhatia15bf5ac2014-07-21 20:06:59 -040035
Scott Baker02b59522014-09-17 22:18:46 -070036 # deprecated in django 1.7 in favor of get_queryset().
Scott Baker92f14222014-09-12 12:57:27 -070037 def get_query_set(self):
38 return self.get_queryset()
39
Sapan Bhatia15bf5ac2014-07-21 20:06:59 -040040# This manager will be inherited by all subclasses because
41# the core model is abstract.
Sapan Bhatia4eb663a2014-04-29 14:26:10 -040042class PlCoreBaseManager(models.Manager):
Scott Baker92f14222014-09-12 12:57:27 -070043 def get_queryset(self):
Scott Baker02b59522014-09-17 22:18:46 -070044 parent=super(PlCoreBaseManager, self)
45 if hasattr(parent, "get_queryset"):
46 return parent.get_queryset().filter(deleted=False)
47 else:
48 return parent.get_query_set().filter(deleted=False)
Siobhan Tully4bc09f22013-04-10 21:15:21 -040049
Scott Baker02b59522014-09-17 22:18:46 -070050 # deprecated in django 1.7 in favor of get_queryset().
Scott Baker92f14222014-09-12 12:57:27 -070051 def get_query_set(self):
52 return self.get_queryset()
53
Scott Bakerf27edfe2015-02-10 15:44:30 -080054class PlModelMixIn(object):
Scott Baker1a6a3902014-10-03 00:32:37 -070055 # Provides useful methods for computing which objects in a model have
56 # changed. Make sure to do self._initial = self._dict in the __init__
57 # method.
58
Scott Bakerf27edfe2015-02-10 15:44:30 -080059 # Also includes useful utility, like getValidators
60
Scott Baker1a6a3902014-10-03 00:32:37 -070061 # This is broken out of PlCoreBase into a Mixin so the User model can
62 # also make use of it.
63
64 @property
65 def _dict(self):
66 return model_to_dict(self, fields=[field.name for field in
67 self._meta.fields])
68
Scott Baker55b786a2015-02-02 14:28:35 -080069 def fields_differ(self,f1,f2):
70 if isinstance(f1,datetime.datetime) and isinstance(f2,datetime.datetime) and (timezone.is_aware(f1) != timezone.is_aware(f2)):
71 return True
72 else:
73 return (f1 != f2)
74
Scott Baker1a6a3902014-10-03 00:32:37 -070075 @property
76 def diff(self):
77 d1 = self._initial
78 d2 = self._dict
Scott Baker55b786a2015-02-02 14:28:35 -080079 diffs = [(k, (v, d2[k])) for k, v in d1.items() if self.fields_differ(v,d2[k])]
Scott Baker1a6a3902014-10-03 00:32:37 -070080 return dict(diffs)
81
82 @property
83 def has_changed(self):
84 return bool(self.diff)
85
86 @property
87 def changed_fields(self):
88 return self.diff.keys()
89
Scott Baker1a6a3902014-10-03 00:32:37 -070090 def has_field_changed(self, field_name):
91 return field_name in self.diff.keys()
92
93 def get_field_diff(self, field_name):
94 return self.diff.get(field_name, None)
95
Scott Bakerd48371e2014-11-25 11:35:19 -080096 #classmethod
97 def getValidators(cls):
98 """ primarily for REST API, return a dictionary of field names mapped
99 to lists of the type of validations that need to be applied to
100 those fields.
101 """
102 validators = {}
103 for field in cls._meta.fields:
104 l = []
105 if field.blank==False:
106 l.append("notBlank")
Scott Baker0d4ecd92014-11-26 00:53:19 -0800107 if field.__class__.__name__=="URLField":
108 l.append("url")
Scott Bakerd48371e2014-11-25 11:35:19 -0800109 validators[field.name] = l
110 return validators
111
Scott Bakere5f9d7d2015-02-10 18:24:20 -0800112 def get_backend_icon(self):
113 # returns (icon_name, tooltip)
114 if (self.enacted is not None) and self.enacted >= self.updated or self.backend_status.startswith("1 -"):
115 return ("success", "successfully enacted")
116 else:
117 if ((self.backend_status is not None) and self.backend_status.startswith("0 -")) or self.backend_status == "Provisioning in progress" or self.backend_status=="":
118 return ("clock", html_escape(self.backend_status, quote=True))
119 else:
120 return ("error", html_escape(self.backend_status, quote=True))
121
Scott Bakerf27edfe2015-02-10 15:44:30 -0800122class PlCoreBase(models.Model, PlModelMixIn):
Sapan Bhatia4eb663a2014-04-29 14:26:10 -0400123 objects = PlCoreBaseManager()
Sapan Bhatia15bf5ac2014-07-21 20:06:59 -0400124 deleted_objects = PlCoreBaseDeletionManager()
125
126 # default values for created and updated are only there to keep evolution
127 # from failing.
Scott Baker02b59522014-09-17 22:18:46 -0700128 created = models.DateTimeField(auto_now_add=True, default=timezone.now)
129 updated = models.DateTimeField(auto_now=True, default=timezone.now)
Scott Baker36286b22014-12-01 21:42:59 -0800130 enacted = models.DateTimeField(null=True, blank=True, default=None)
Sapan Bhatiaab66b7d2015-01-23 15:59:55 +0000131 policed = models.DateTimeField(null=True, blank=True, default=None)
Sapan Bhatia31176192015-01-29 20:36:45 +0000132
133 # This is a scratchpad used by the Observer
134 backend_register = models.CharField(max_length=140,
135 default="{}", null=True)
136
Scott Baker19c77962015-02-06 00:11:10 -0800137 backend_status = models.CharField(max_length=1024,
Sapan Bhatia030cede2015-01-23 16:07:24 +0000138 default="0 - Provisioning in progress")
Sapan Bhatiabcc18992014-04-29 10:32:14 -0400139 deleted = models.BooleanField(default=False)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400140
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400141 class Meta:
Sapan Bhatia3089d832014-04-29 14:36:51 -0400142 # Changing abstract to False would require the managers of subclasses of
143 # PlCoreBase to be customized individually.
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400144 abstract = True
145 app_label = "core"
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400146
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400147 def __init__(self, *args, **kwargs):
148 super(PlCoreBase, self).__init__(*args, **kwargs)
Scott Bakerf27edfe2015-02-10 15:44:30 -0800149 self._initial = self._dict # for PlModelMixIn
Scott Bakerf3f895c2014-09-23 22:41:17 -0700150 self.silent = False
Scott Baker13acdd62013-05-08 17:42:56 -0700151
Tony Mack5b061472014-02-04 07:57:10 -0500152 def can_update(self, user):
Tony Mack3428e6e2015-02-08 21:38:41 -0500153 return user.can_update_root()
Scott Baker1a6a3902014-10-03 00:32:37 -0700154
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400155 def delete(self, *args, **kwds):
Scott Baker6ecd4262014-01-21 23:15:21 -0800156 # so we have something to give the observer
Sapan Bhatia77d1d892014-07-21 20:07:23 -0400157 purge = kwds.get('purge',False)
Scott Baker3d2d3e42014-10-09 16:22:00 -0700158 if purge:
159 del kwds['purge']
Scott Baker6594bea2014-09-23 16:04:36 -0700160 silent = kwds.get('silent',False)
Scott Baker3d2d3e42014-10-09 16:22:00 -0700161 if silent:
162 del kwds['silent']
Sapan Bhatia77d1d892014-07-21 20:07:23 -0400163 try:
164 purge = purge or observer_disabled
165 except NameError:
166 pass
Scott Baker6594bea2014-09-23 16:04:36 -0700167
Sapan Bhatia77d1d892014-07-21 20:07:23 -0400168 if (purge):
169 super(PlCoreBase, self).delete(*args, **kwds)
Sapan Bhatiac8602432014-04-29 20:33:51 -0400170 else:
171 self.deleted = True
172 self.enacted=None
Scott Baker6594bea2014-09-23 16:04:36 -0700173 self.save(update_fields=['enacted','deleted'], silent=silent)
Sapan Bhatiadbaf1932013-09-03 11:28:52 -0400174
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400175 def save(self, *args, **kwargs):
Scott Bakerf3f895c2014-09-23 22:41:17 -0700176 # let the user specify silence as either a kwarg or an instance varible
177 silent = self.silent
Scott Baker6594bea2014-09-23 16:04:36 -0700178 if "silent" in kwargs:
Scott Bakerf3f895c2014-09-23 22:41:17 -0700179 silent=silent or kwargs.pop("silent")
Scott Baker6594bea2014-09-23 16:04:36 -0700180
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400181 super(PlCoreBase, self).save(*args, **kwargs)
Sapan Bhatiac8602432014-04-29 20:33:51 -0400182
Sapan Bhatia9c2c8fa2013-10-16 13:26:05 -0400183 # This is a no-op if observer_disabled is set
Sapan Bhatia03388fa2015-02-10 11:46:51 -0500184 # if not silent:
185 # notify_observer()
Sapan Bhatia66f4e612013-07-02 12:12:38 -0400186
Scott Baker5e5f4552014-10-03 14:48:06 -0700187 self._initial = self._dict
Scott Baker13acdd62013-05-08 17:42:56 -0700188
Tony Mack5b061472014-02-04 07:57:10 -0500189 def save_by_user(self, user, *args, **kwds):
Scott Bakere5f41b02014-10-02 22:50:18 -0700190 if not self.can_update(user):
Scott Baker1bffe942014-10-06 22:58:48 -0700191 if getattr(self, "_cant_update_fieldName", None) is not None:
192 raise PermissionDenied("You do not have permission to update field %s on object %s" % (self._cant_update_fieldName, self.__class__.__name__))
193 else:
194 raise PermissionDenied("You do not have permission to update %s objects" % self.__class__.__name__)
Scott Baker1a6a3902014-10-03 00:32:37 -0700195
Scott Bakere5f41b02014-10-02 22:50:18 -0700196 self.save(*args, **kwds)
Tony Mack5b061472014-02-04 07:57:10 -0500197
Tony Mack332ee1d2014-02-04 15:33:45 -0500198 def delete_by_user(self, user, *args, **kwds):
Scott Bakere5f41b02014-10-02 22:50:18 -0700199 if not self.can_update(user):
200 raise PermissionDenied("You do not have permission to delete %s objects" % self.__class__.__name__)
201 self.delete(*args, **kwds)
Tony Mack332ee1d2014-02-04 15:33:45 -0500202
Scott Baker5e5f4552014-10-03 14:48:06 -0700203 @classmethod
204 def select_by_user(cls, user):
205 # This should be overridden by descendant classes that want to perform
206 # filtering of visible objects by user.
207 return cls.objects.all()
208
Sapan Bhatiae33b9dc2014-11-12 10:06:23 -0500209 @classmethod
210 def is_ephemeral(cls):
Sapan Bhatiaf6613e32014-11-12 10:38:23 -0500211 return cls in ephemeral_models
Sapan Bhatiae33b9dc2014-11-12 10:06:23 -0500212
Scott Baker17dbb022014-11-25 00:49:17 -0800213
214
Scott Baker13acdd62013-05-08 17:42:56 -0700215
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400216
217