class StructureModel(models.Model): """ Generic structure model. Provides transparent interaction with base entities and relations like customer. """ objects = StructureManager() class Meta: abstract = True def __getattr__(self, name): # add additional properties to the object according to defined Permissions class fields = ('customer', 'project') if name in fields: try: path = getattr(self.Permissions, name + '_path') except AttributeError: pass else: if not path == 'self' and '__' in path: return reduce(getattr, path.split('__'), self) raise AttributeError( "'%s' object has no attribute '%s'" % (self._meta.object_name, name) )
class Project( ProjectDetailsMixin, core_models.UuidMixin, core_models.DescendantMixin, core_models.BackendMixin, quotas_models.ExtendableQuotaModelMixin, PermissionMixin, StructureLoggableMixin, TimeStampedModel, StructureModel, SoftDeletableModel, ): class Permissions: customer_path = 'customer' project_path = 'self' GLOBAL_COUNT_QUOTA_NAME = 'nc_global_project_count' OECD_FOS_2007_CODES = ( ('1.1', _('Mathematics')), ('1.2', _('Computer and information sciences')), ('1.3', _('Physical sciences')), ('1.4', _('Chemical sciences')), ('1.5', _('Earth and related environmental sciences')), ('1.6', _('Biological sciences')), ('1.7', _('Other natural sciences')), ('2.1', _('Civil engineering')), ( '2.2', _('Electrical engineering, electronic engineering, information engineering' ), ), ('2.3', _('Mechanical engineering')), ('2.4', _('Chemical engineering')), ('2.5', _('Materials engineering')), ('2.6', _('Medical engineering')), ('2.7', _('Environmental engineering')), ('2.8', _('Systems engineering')), ('2.9', _('Environmental biotechnology')), ('2.10', _('Industrial biotechnology')), ('2.11', _('Nano technology')), ('2.12', _('Other engineering and technologies')), ('3.1', _('Basic medicine')), ('3.2', _('Clinical medicine')), ('3.3', _('Health sciences')), ('3.4', _('Health biotechnology')), ('3.5', _('Other medical sciences')), ('4.1', _('Agriculture, forestry, and fisheries')), ('4.2', _('Animal and dairy science')), ('4.3', _('Veterinary science')), ('4.4', _('Agricultural biotechnology')), ('4.5', _('Other agricultural sciences')), ('5.1', _('Psychology')), ('5.2', _('Economics and business')), ('5.3', _('Educational sciences')), ('5.4', _('Sociology')), ('5.5', _('Law')), ('5.6', _('Political science')), ('5.7', _('Social and economic geography')), ('5.8', _('Media and communications')), ('5.9', _('Other social sciences')), ('6.1', _('History and archaeology')), ('6.2', _('Languages and literature')), ('6.3', _('Philosophy, ethics and religion')), ('6.4', _('Arts (arts, history of arts, performing arts, music)')), ('6.5', _('Other humanities')), ) class Quotas(quotas_models.QuotaModelMixin.Quotas): enable_fields_caching = False nc_resource_count = quotas_fields.CounterQuotaField( target_models=lambda: BaseResource.get_all_models(), path_to_scope='project', ) customer = models.ForeignKey( Customer, verbose_name=_('organization'), related_name='projects', on_delete=models.PROTECT, ) tracker = FieldTracker() type = models.ForeignKey( ProjectType, verbose_name=_('project type'), blank=True, null=True, on_delete=models.PROTECT, ) oecd_fos_2007_code = models.CharField(choices=OECD_FOS_2007_CODES, null=True, blank=True, max_length=80) objects = SoftDeletableManager() structure_objects = StructureManager() @property def is_expired(self): return self.end_date and self.end_date <= timezone.datetime.today( ).date() @property def full_name(self): return self.name def get_users(self, role=None): query = Q( projectpermission__project=self, projectpermission__is_active=True, ) if role: query = query & Q(projectpermission__role=role) return get_user_model().objects.filter(query).order_by('username') @transaction.atomic() def _soft_delete(self, using=None): """ Method for project soft delete. It doesn't delete a project, only mark as 'removed', but it sends signals """ signals.pre_delete.send(sender=self.__class__, instance=self, using=using) self.is_removed = True self.save(using=using) signals.post_delete.send(sender=self.__class__, instance=self, using=using) def delete(self, using=None, soft=True, *args, **kwargs): """Use soft delete, i.e. mark a project as 'removed'.""" if soft: self._soft_delete(using) else: return super(SoftDeletableModel, self).delete(using=using, *args, **kwargs) def __str__(self): return '%(name)s | %(customer)s' % { 'name': self.name, 'customer': self.customer.name, } def can_user_update_quotas(self, user): return user.is_staff or self.customer.has_user(user, CustomerRole.OWNER) def can_manage_role(self, user, role=None, timestamp=False): if user.is_staff: return True if self.customer.has_user(user, CustomerRole.OWNER, timestamp): return True return role == ProjectRole.ADMINISTRATOR and self.has_user( user, ProjectRole.MANAGER, timestamp) def get_log_fields(self): return ('uuid', 'customer', 'name') def get_parents(self): return [self.customer] class Meta: base_manager_name = 'objects'
class Project( core_models.DescribableMixin, core_models.UuidMixin, core_models.NameMixin, core_models.DescendantMixin, quotas_models.ExtendableQuotaModelMixin, PermissionMixin, StructureLoggableMixin, TimeStampedModel, StructureModel, SoftDeletableModel, ): class Permissions: customer_path = 'customer' project_path = 'self' GLOBAL_COUNT_QUOTA_NAME = 'nc_global_project_count' class Quotas(quotas_models.QuotaModelMixin.Quotas): enable_fields_caching = False nc_resource_count = quotas_fields.CounterQuotaField( target_models=lambda: ResourceMixin.get_all_models(), path_to_scope='project', ) nc_app_count = quotas_fields.CounterQuotaField( target_models=lambda: ApplicationMixin.get_all_models(), path_to_scope='project', ) nc_vm_count = quotas_fields.CounterQuotaField( target_models=lambda: VirtualMachine.get_all_models(), path_to_scope='project', ) nc_private_cloud_count = quotas_fields.CounterQuotaField( target_models=lambda: PrivateCloud.get_all_models(), path_to_scope='project', ) nc_storage_count = quotas_fields.CounterQuotaField( target_models=lambda: Storage.get_all_models(), path_to_scope='project', ) nc_volume_count = quotas_fields.CounterQuotaField( target_models=lambda: Volume.get_all_models(), path_to_scope='project', ) nc_snapshot_count = quotas_fields.CounterQuotaField( target_models=lambda: Snapshot.get_all_models(), path_to_scope='project', ) nc_volume_size = quotas_fields.TotalQuotaField( target_models=lambda: Volume.get_all_models(), path_to_scope='project', target_field='size', ) nc_snapshot_size = quotas_fields.TotalQuotaField( target_models=lambda: Snapshot.get_all_models(), path_to_scope='project', target_field='size', ) certifications = models.ManyToManyField(to='ServiceCertification', related_name='projects', blank=True) customer = models.ForeignKey( Customer, verbose_name=_('organization'), related_name='projects', on_delete=models.PROTECT, ) tracker = FieldTracker() type = models.ForeignKey( ProjectType, verbose_name=_('project type'), blank=True, null=True, on_delete=models.PROTECT, ) objects = SoftDeletableManager() structure_objects = StructureManager() @property def full_name(self): return self.name def get_users(self, role=None): query = Q( projectpermission__project=self, projectpermission__is_active=True, ) if role: query = query & Q(projectpermission__role=role) return get_user_model().objects.filter(query).order_by('username') @transaction.atomic() def _soft_delete(self, using=None): """ Method for project soft delete. It doesn't delete a project, only mark as 'removed', but it sends needed signals and delete ServiceProjectLink objects """ signals.pre_delete.send(sender=self.__class__, instance=self, using=using) for model in ServiceProjectLink.get_all_models(): for spl in model.objects.filter(project=self): spl.delete() self.is_removed = True self.save(using=using) signals.post_delete.send(sender=self.__class__, instance=self, using=using) def delete(self, using=None, soft=True, *args, **kwargs): """Use soft delete, i.e. mark a project as 'removed'.""" if soft: self._soft_delete(using) else: return super(SoftDeletableModel, self).delete(using=using, *args, **kwargs) def __str__(self): return '%(name)s | %(customer)s' % { 'name': self.name, 'customer': self.customer.name, } def can_user_update_quotas(self, user): return user.is_staff or self.customer.has_user(user, CustomerRole.OWNER) def can_manage_role(self, user, role, timestamp=False): """ Checks whether user can grant/update/revoke project permissions for specific role. `timestamp` can have following values: - False - check whether user can manage permissions at the moment. - None - check whether user can permanently manage permissions. - Datetime object - check whether user will be able to manage permissions at specific timestamp. """ if user.is_staff: return True if self.customer.has_user(user, CustomerRole.OWNER, timestamp): return True return role == ProjectRole.ADMINISTRATOR and self.has_user( user, ProjectRole.MANAGER, timestamp) def get_log_fields(self): return ('uuid', 'customer', 'name') def get_parents(self): return [self.customer] def get_children(self): """ Get all service project links connected to current project """ return itertools.chain.from_iterable( m.objects.filter(project=self) for m in ServiceProjectLink.get_all_models()) class Meta: base_manager_name = 'objects'