def get_default_namespaces(): """ Whether owner or owner-domain (org, dept, etc.), return the default Namespace instance as a singleton list. It is created by default if SMARTMODELS_NAMESPACE_MODEL model DoesNotExists Also make the sentinel owner belong to the sentinel org. """ namespace, created = get_namespace_model()._objects.get_or_create( **{ get_setting('NAMESPACE_PK_FIELD'): get_setting('SENTINEL_UID'), }) return list((namespace, ))
def is_sentinel(instance): """ Whether instance is the sentinel for the user or namespace model. :param instance: instance of User or Namespace. :return: bool """ return _are_services(instance, uid=get_setting('SENTINEL_UID'))
def prepare_shared_smart_fields(sender, instance, **kwargs): """ Ensuring Resource subclasses will be saved with (*)all smart fields correctly set. Whether to require the smart fields' values from the api user (if `MODELS_DEFAULT_REQUIRED=True`)? Or to preset them with the defaults when the api user has not supplied them? (*) Only set the shared smart fields. Regular smart fields assumed already set by homologue method `prepare_smart_fields` for SmartModel subclasses. (*) We'nt aware whether it's a update or create op! Which fields need persisting to db is left up to high level api. """ if issubclass(sender, Resource): namespaces = getattr(instance, get_namespaces_manager_name()) # require the setting of all Resource fields by the api user # if requested so (`MODELS_DEFAULT_REQUIRED=True) otherwise, # use our own defaults presets. if get_setting('DEFAULT_REQUIRED'): assert namespaces, ( '{model_class}(SmartModel) instance missing "namespaces" attribute.' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=instance.__class__.__name__)) assert namespaces.exists(), ( 'The `namespaces` smart model field mustn\'t be empty since SMARTMODELS_DEFAULT_REQUIRED=True". ' 'Please supply some {model_class} instances.' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=type(namespaces).__name__)) # forbid CRUDing namespace instances, superusers excepted. if issubclass(sender, Namespace): drop_perms(sender)
def delete(self, using=None, keep_parents=False): """ Model.delete() override that also mark model as deleted and by whom. Requires the `deleted_by` field to be set by the caller, if SMARTMODELS_DEFAULT_REQUIRED=True. Owner of the model is changed to the sentinel owner by the ORM behind the scene through `on_delete=models.SET(get_sentinel_user)`. Cf the owner field definition. """ # this is a guard to ensure `deleted_by` is set # so that we know we deletes an instance if get_setting('DEFAULT_REQUIRED'): assert self.deleted_by, ( '{model_class}(SmartModel) instance missing "deleted_by" attribute.' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=self.__class__.__name__)) self.deleted_at = timezone.now() self.owner = get_sentinel_user() # calling save instead of regular `delete()` method, # will route to the `smartmodels.models.prepare_smart_fields()` pre_save signal handler # also, let's manually fire the `post_delete` signal to leave change to listeners to cope with the deletion. post_delete.send(sender=self.__class__, instance=self, deleted_by=self.deleted_by) self.save()
def get_sentinel_user(): """ Sentinel instance, of the AUTH_USER_MODEL model. """ # sentinel is hidden from the regular `objects` manager, # use the default manager user, created = get_user_model()._objects.get_or_create( **{get_owner_pk_field(): get_setting('SENTINEL_UID')}) return user
def active(self): """ Hide 1) the deleted instances 2) those whose creator (owner) has been deleted and 3) those belonging to the sentinel owner. """ # FIXME: implement 2-3). # FIXME: get_sentinel_owner() exception trying to get instance 'coz models not loaded yet qs = self owner_pk_field = get_owner_pk_field() sentinel_filter = {"owner__{}".format(owner_pk_field): get_setting('SENTINEL_UID')} services_filter = {"owner__{}__in".format(owner_pk_field): get_setting('SERVICE_UIDS')} # hide what should be hidden qs = qs.exclude(Q(**sentinel_filter) | Q(owner__is_active=False,)) if get_setting('HIDE_DELETED'): qs = qs.exclude(Q(deleted_at__isnull=False)) if get_setting('HIDE_SERVICE_OWNERS'): qs = qs.exclude(Q(**services_filter) | Q(deleted_at__isnull=False)) return qs
class Resource(SmartModel): """ Resource instances can be `owned` by several namespaces at once. If no valid namespace is set on creation, a resource inherit by default the namespaces of the owner that creates it. """ # defaults to `shared_model.Namespace` instances. cf. smartmodels.settings namespaces = models.ManyToManyField( get_setting('NAMESPACE_MODEL'), related_name='%(class)ss_owned', help_text=_("Visibility domain: org, district, domain, etc.")) class Meta: abstract = True
def delete(self, deleted_by=None, **kwargs): """ Fake-delete an entire queryset. Hooked when call looks like: SmartX.objects.filter(**opts).delete() :return: Nothing """ if get_setting('DEFAULT_REQUIRED'): assert deleted_by, ( '{model_class}(SmartModel) manager method `delete()` missing "deleted_by" attribute.' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=self.__class__.__name__ ) ) return super(SmartQuerySet, self)\ .update(deleted_at=timezone.now(), deleted_by=deleted_by, owner=get_sentinel_user(), **kwargs)
def get_namespace_model(): """ Return the Owner model class (not string) that is active in this project. """ model_string = get_setting('NAMESPACE_MODEL') try: return django_apps.get_model(model_string, require_ready=False) except ValueError: raise ImproperlyConfigured( "SMARTMODELS_NAMESPACE_MODEL must be of the form 'app_label.model_name'" ) except LookupError: raise ImproperlyConfigured( "SMARTMODELS_NAMESPACE_MODEL refers to model '%s' that has not been installed" % model_string)
def prepare_smart_fields(sender, instance, **kwargs): """ Ensures SmartModel subclasses will be saved with (*)all smart fields correctly set. Will require the smart fields' values set from higher lever API if `MODELS_DEFAULT_REQUIRED=True` Set `MODELS_DEFAULT_REQUIRED=False` to emulate regular models (ignore the smart fields). (*) We are'nt aware whether it's a update or create op, nor what owner achieves it! (*) Values for `created_by`, `updated_by` `deleted_by` must be set by the higher level api. """ # TODO: delete not handled yet ! if issubclass(sender, SmartModel): # require the setting of all Resource fields by the api owner # if requested so (`MODELS_DEFAULT_REQUIRED=True) otherwise, # use our own defaults presets. excluded = is_sentinel(instance) or is_superuser( instance) or is_service(instance) if get_setting('DEFAULT_REQUIRED') and not excluded: assert instance.owner, ( '{model_class}(SmartModel) instance missing "owner" attribute. ' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=instance.__class__.__name__)) if not instance.pk: assert instance.created_by and instance.updated_by, ( '{model_class}(SmartModel) instance missing "created_by" or "updated_by" attribute. ' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=instance.__class__.__name__)) else: assert instance.updated_by or instance.deleted_by, ( '{model_class}(SmartModel) instance missing "updated_by" or "deleted_by" attribute. ' 'To use the builtin defaults, set `SMARTMODELS_DEFAULT_REQUIRED=False`' 'in Django settings'.format( model_class=instance.__class__.__name__)) # use the builtin defaults, # because MODELS_DEFAULT_REQUIRED=True else: time = timezone.now() smart_fields = dict(updated_at=time) if not instance.pk: smart_fields.update(created_at=time) for attr, value in smart_fields.items(): setattr(instance, attr, value)
class AbstractNamespace(models.Model): """ Top level domain (org, department, topic, etc.) which defines: 1) the viewing scope of resources (Resource instances) created by users, 2) the high level entity users are contributing to by creating resources. When picked up by Django settings (`SMARTMODELS_NAMESPACE_MODEL != AUTH_USER_MODEL), it means that the `distributed mode is active. """ # TODO: Make the `name` field a configurable setting via custom metaclass slug = models.SlugField( _("Unique ID"), default='', max_length=get_setting('NAMESPACE_MAX_LENGTH'), help_text= _("Unique ID for this namespace. Configurable via the SMARTMODELS_OWNER_PK_FIELD setting." ), unique=True, blank=False, null=False, editable=True, error_messages=dict(unique=_("This slug is not available"))) users = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name= '%(class)ss', # eg. user.namespaces, user.orgs, user.domains, etc. help_text=_("Contributors (users) to this org, department, etc.")) objects = NamespaceManager() _objects = models.Manager() def __str__(self): return self.slug class Meta: abstract = True def clean(self): self.slug = slugify(self.slug, allow_unicode=True)
def active(self): return self.exclude( **{ get_setting('NAMESPACE_PK_FIELD'): get_setting('SENTINEL_UID'), })
def is_service(instance): return _are_services(instance, uids=get_setting('SERVICE_UIDS'))
def get_queryset(self): if get_setting('NAMESPACE_MODEL') == settings.AUTH_USER_MODEL: return get_default_namespaces() return super(NamespaceViewSet, self).get_queryset()