class AbstractTemplateBase(Base): ct = CTypeForeignKey(editable=False).set_tags(viewable=False) # TODO: avoid deletion of status status_id = PositiveIntegerField(editable=False).set_tags(viewable=False) creation_label = pgettext_lazy('billing', 'Create a template') save_label = pgettext_lazy('billing', 'Save the template') generate_number_in_create = False _verbose_status_cache = None class Meta(Base.Meta): abstract = True verbose_name = pgettext_lazy('billing', 'Template') verbose_name_plural = pgettext_lazy('billing', 'Templates') def get_absolute_url(self): return reverse('billing__view_template', args=(self.id,)) @staticmethod def get_clone_absolute_url(): return '' def get_edit_absolute_url(self): return reverse('billing__edit_template', args=(self.id,)) def get_delete_absolute_url(self): # Means that TemplateBase can not be deleted directly # (because it is closely linked to its RecurrentGenerator) return '' @staticmethod def get_lv_absolute_url(): return reverse('billing__list_templates') def create_entity(self): "This method is used by the generation job" instance_class = self.ct.model_class() instance = instance_class() instance.build(self) # Common rules for the recurrent generation of a "base" object for billing app. # See base's child for specific rules instance.generate_number() # TODO: user configurable rules ??? instance.expiration_date = instance.issuing_date + timedelta(days=30) instance.additional_info = self.additional_info instance.payment_terms = self.payment_terms instance.save() return instance
class WaitingAction(CremeModel): action = CharField( _(u'Action'), max_length=100 ) # Action (i.e: create, update...) # TODO: int instead ?? # TODO: split into 2 CharFields 'fetcher' & 'input' ? # NB: - If default backend (subject="*"): fetcher_name. # - If not 'fetcher_name - input_name' (i.e: email - raw, email - infopath, sms - raw...). source = CharField(_(u'Source'), max_length=100) # raw_data = TextField(blank=True, null=True) # Pickled data raw_data = BinaryField(blank=True, null=True) # Pickled data ct = CTypeForeignKey(verbose_name=_( u'Type of resource')) # Redundant, but faster bd recovery subject = CharField(_(u'Subject'), max_length=100) user = CremeUserForeignKey(verbose_name=_(u'Owner'), blank=True, null=True, default=None) # If sandbox per user class Meta: app_label = 'crudity' verbose_name = _(u'Waiting action') verbose_name_plural = _(u'Waiting actions') # def get_data(self): @property def data(self): # data = loads(self.raw_data.encode('utf-8')) data = loads(self.raw_data) # if isinstance(data, dict): # data = { # k: v.decode('utf8') if isinstance(v, str) else v # for k, v in data.items() # } return data # def set_data(self, data): @data.setter def data(self, data): self.raw_data = dumps(data) def can_validate_or_delete(self, user): """self.user not None means that sandbox is by user""" if self.user is not None and self.user != user and not user.is_superuser: return False, ugettext( u'You are not allowed to validate/delete the waiting action <{}>' ).format(self.id) return True, ugettext(u'OK')
class SimpleBillingAlgo(Model): organisation = ForeignKey(settings.PERSONS_ORGANISATION_MODEL, verbose_name=_('Organisation'), on_delete=CASCADE) last_number = IntegerField() prefix = CharField(_('Invoice prefix'), max_length=400) ct = CTypeForeignKey() ALGO_NAME = 'SIMPLE_ALGO' # TODO: prefix with app name class Meta: app_label = 'billing' unique_together = ("organisation", "last_number", "ct") def __str__(self): return 'SimpleBillingAlgo(organisation="{}", ct="{}")'.format( self.organisation, self.ct)
class ConfigBillingAlgo(CremeModel): organisation = ForeignKey(settings.PERSONS_ORGANISATION_MODEL, verbose_name=_('Organisation'), on_delete=CASCADE) name_algo = CharField(_('Algo name'), max_length=400) ct = CTypeForeignKey() class Meta: app_label = 'billing' # TODO unique_together = ("organisation", "name_algo", "ct") ?? def __str__(self): return 'ConfigBillingAlgo(organisation="{}", name_algo="{}", ct="{}")'.format( self.organisation, self.name_algo, self.ct, )
class ConfigBillingAlgo(CremeModel): organisation = models.ForeignKey( settings.PERSONS_ORGANISATION_MODEL, verbose_name=_('Organisation'), on_delete=models.CASCADE, ) name_algo = models.CharField(_('Algo name'), max_length=400) ct = CTypeForeignKey() class Meta: app_label = 'billing' # TODO unique_together = ("organisation", "name_algo", "ct") ?? def __str__(self): return (f'ConfigBillingAlgo(' f'organisation="{self.organisation}", ' f'name_algo="{self.name_algo}", ' f'ct="{self.ct}"' f')')
class Migration(migrations.Migration): dependencies = [ ('crudity', '0001_initial'), ] operations = [ migrations.RenameField( model_name='waitingaction', old_name='data', new_name='raw_data', ), migrations.AlterField( model_name='waitingaction', name='ct', field=CTypeForeignKey(on_delete=CASCADE, to='contenttypes.ContentType', verbose_name='Type of resource'), ), ]
class CremeExchangeMapping(CremeModel): creme_entity_id = IntegerField(u'Creme entity pk', unique=True) creme_entity_ct = CTypeForeignKey( verbose_name=u'Creme entity ct' ) # For filtering when the entity was deleted exchange_entity_id = CharField(u'Exchange entity pk', max_length=64, unique=True) synced = BooleanField(u'Already synced on server', default=False) is_creme_modified = BooleanField(u'Modified by creme?', default=False) was_deleted = BooleanField( u'Was deleted by creme?', default=False ) # Seems redundant with is_deleted but isn't in case of real deletion user = ForeignKey(settings.AUTH_USER_MODEL, verbose_name=u'Belongs to', on_delete=CASCADE) creme_entity_repr = CharField(u'Verbose entity representation', max_length=200, null=True, blank=True, default=u"") # IHM/User purposes def __str__(self): return u"<CremeExchangeMapping ce_id: <%s>, ex_id: <%s>, belongs to %s>" % ( self.creme_entity_id, self.exchange_entity_id, self.user) class Meta: app_label = 'activesync' # verbose_name = u"" # verbose_name_plural = u"" def get_entity(self): entity = None if not self.creme_entity_id: return entity try: entity = CremeEntity.objects.get( pk=self.creme_entity_id).get_real_entity() except CremeEntity.DoesNotExist: pass return entity
class SimpleBillingAlgo(models.Model): organisation = models.ForeignKey( settings.PERSONS_ORGANISATION_MODEL, verbose_name=_('Organisation'), on_delete=models.CASCADE, ) last_number = models.IntegerField() prefix = models.CharField(_('Invoice prefix'), max_length=400) ct = CTypeForeignKey() ALGO_NAME = 'SIMPLE_ALGO' # TODO: prefix with app name class Meta: app_label = 'billing' unique_together = ('organisation', 'last_number', 'ct') def __str__(self): return (f'SimpleBillingAlgo(' f'organisation="{self.organisation}", ' f'ct="{self.ct}", ' f'last_number={self.last_number}, ' f'prefix="{self.prefix}"' f')')
class AbstractRecurrentGenerator(CremeEntity): name = models.CharField(_('Name of the generator'), max_length=100, blank=True) # description = models.TextField(_('Description'), blank=True) first_generation = models.DateTimeField(_('Date of the first generation')) last_generation = models.DateTimeField( _('Date of the last generation'), null=True, editable=False, ) periodicity = DatePeriodField(_('Periodicity of the generation')) ct = CTypeForeignKey(verbose_name=_('Type of the recurrent resource'), editable=False) template = models.ForeignKey( CremeEntity, verbose_name=_('Related model'), related_name='template_set', editable=False, on_delete=models.CASCADE, ) is_working = models.BooleanField(_('Active ?'), editable=False, default=True) # TODO: useful ? creation_label = _('Create a generator') save_label = _('Save the generator') class Meta: abstract = True manager_inheritance_from_future = True app_label = 'recurrents' verbose_name = _('Recurrent generator') verbose_name_plural = _('Recurrent generators') ordering = ('name', ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__init_refreshing_cache() def __init_refreshing_cache(self): self._old_first_generation = self.first_generation self._old_periodicity = self.periodicity # TODO: is_working when it is used def __str__(self): return self.name def get_absolute_url(self): return reverse('recurrents__view_generator', args=(self.id, )) @staticmethod def get_create_absolute_url(): return reverse('recurrents__create_generator') def get_edit_absolute_url(self): return reverse('recurrents__edit_generator', args=(self.id, )) @staticmethod def get_lv_absolute_url(): return reverse('recurrents__list_generators') def save(self, *args, **kwargs): from ..creme_jobs import recurrents_gendocs_type created = bool(not self.pk) super().save(*args, **kwargs) if created or self._old_first_generation != self.first_generation or \ self._old_periodicity != self.periodicity: recurrents_gendocs_type.refresh_job() self.__init_refreshing_cache()
class AbstractTemplateBase(Base): ct = CTypeForeignKey(editable=False).set_tags(viewable=False) status_id = PositiveIntegerField(editable=False).set_tags( viewable=False) # TODO: avoid deletion of status creation_label = pgettext_lazy('billing', 'Create a template') save_label = pgettext_lazy('billing', 'Save the template') _verbose_status_cache = None class Meta(Base.Meta): abstract = True verbose_name = pgettext_lazy('billing', 'Template') verbose_name_plural = pgettext_lazy('billing', 'Templates') def get_absolute_url(self): return reverse('billing__view_template', args=(self.id, )) @staticmethod def get_clone_absolute_url(): return '' def get_edit_absolute_url(self): return reverse('billing__edit_template', args=(self.id, )) def get_delete_absolute_url(self): # Means that TemplateBase can not be deleted directly # (because it is closely linked to its RecurrentGenerator) return '' @staticmethod def get_lv_absolute_url(): return reverse('billing__list_templates') # def get_verbose_status(self): # warnings.warn('models.AbstractTemplateBase.get_verbose_status() is deprecated ; ' # 'use function_fields.TemplateBaseVerboseStatusField instead.', # DeprecationWarning # ) # # vstatus = self._verbose_status_cache # # if vstatus is None or vstatus.id != self.status_id: # status_model = self.ct.model_class()._meta.get_field('status').remote_field.model # # try: # vstatus = status_model.objects.get(id=self.status_id) # except status_model.DoesNotExist as e: # logger.warning('Invalid status in TemplateBase(id=%s) [%s]', self.id, e) # vstatus = status_model(id=self.status_id, name='') # # self._verbose_status_cache = vstatus # # return vstatus.name # @property # def verbose_status(self): # warnings.warn('AbstractTemplateBase.verbose_status is deprecated ' # '(see get_verbose_status() warning).', # DeprecationWarning # ) # # return self.get_verbose_status() def create_entity(self): "This method is used by the generation job" instance_class = self.ct.model_class() instance = instance_class() instance.build(self) # Common rules for the recurrent generation of a "base" object for billing app. # See base's child for specific rules instance.generate_number() instance.expiration_date = instance.issuing_date + timedelta( days=30) # TODO: user configurable rules ??? instance.additional_info = self.additional_info instance.payment_terms = self.payment_terms instance.save() return instance
class ActObjectivePatternComponent(CremeModel): pattern = ForeignKey(ActObjectivePattern, related_name='components', editable=False, on_delete=CASCADE) parent = ForeignKey('self', null=True, related_name='children', editable=False, on_delete=CASCADE) name = CharField(_('Name'), max_length=_NAME_LENGTH) ctype = CTypeForeignKey(verbose_name=_('Counted type'), null=True, blank=True, editable=False) filter = ForeignKey( EntityFilter, verbose_name=_('Filter on counted entities'), null=True, blank=True, on_delete=PROTECT, editable=False, ) success_rate = PositiveIntegerField( _('Success rate')) # TODO: smallinteger ?? _children_cache = None class Meta: app_label = "commercial" def __str__(self): return self.name # TODO: delete this code with new ForeignKey in Django1.3 ?? (maybe it causes more queries) # def delete(self): def delete(self, *args, **kwargs): def find_node(nodes, pk): for node in nodes: if node.id == pk: return node found = find_node(node.get_children(), pk) if found: return found def flatten_node_ids(node, node_list): node_list.append(node.id) for child in node.get_children(): flatten_node_ids(child, node_list) children2del = [] # TODO: tree may inherit from a smart tree structure with right method like found()/flatten() etc... flatten_node_ids( find_node(self.pattern.get_components_tree(), self.id), children2del) ActObjectivePatternComponent.objects.filter( pk__in=children2del).delete() # NB super(ActObjectivePatternComponent, self).delete() is not called def get_children(self): children = self._children_cache if children is None: self._children_cache = children = list(self.children.all()) return children def get_related_entity(self): # NB: see delete_related_to_entity() return self.pattern def clone(self, pattern, parent=None): """Clone the entire hierarchy of the node wherever is it""" own_parent = None if self.parent and not parent: own_parent = self.parent.clone(pattern) me = ActObjectivePatternComponent.objects.create( pattern=pattern, parent=own_parent or parent, name=self.name, ctype_id=self.ctype_id, filter_id=self.filter_id, success_rate=self.success_rate, ) for sub_aopc in self.children.all(): sub_aopc.clone(pattern, me)
class ActObjective(CremeModel): name = CharField(_('Name'), max_length=_NAME_LENGTH) act = ForeignKey( settings.COMMERCIAL_ACT_MODEL, related_name='objectives', editable=False, on_delete=CASCADE, ) counter = PositiveIntegerField(_('Counter'), default=0, editable=False) counter_goal = PositiveIntegerField(_('Value to reach'), default=1) ctype = CTypeForeignKey(verbose_name=_('Counted type'), null=True, blank=True, editable=False) filter = ForeignKey( EntityFilter, verbose_name=_('Filter on counted entities'), null=True, blank=True, on_delete=PROTECT, editable=False, ) creation_label = _('Create an objective') save_label = _('Save the objective') _count_cache = None class Meta: app_label = 'commercial' verbose_name = _('Commercial Objective') verbose_name_plural = _('Commercial Objectives') # ordering = ('name',) TODO ? def __str__(self): return self.name def get_edit_absolute_url(self): return reverse('commercial__edit_objective', args=(self.id, )) def get_related_entity(self): # NB: for generic views return self.act def get_count(self): # TODO: property ?? count = self._count_cache if count is None: ctype = self.ctype if ctype: if self.filter: qs = ctype.model_class().objects.filter( is_deleted=False, # TODO: test deleted=False relations__type=REL_SUB_COMPLETE_GOAL, relations__object_entity=self.act_id, ) count = self.filter.filter(qs).count() else: count = Relation.objects.filter(type=REL_SUB_COMPLETE_GOAL, object_entity=self.act_id, subject_entity__is_deleted=False, subject_entity__entity_type=ctype, ) \ .count() else: count = self.counter self._count_cache = count return count @property def reached(self): return self.get_count() >= self.counter_goal
class UserSynchronizationHistory(CremeModel): user = ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_(u'User'), on_delete=CASCADE) entity_repr = CharField( u'Entity', max_length=200, default=None, blank=True, null=True ) # Saving the representation of the entity in case it was deleted entity_pk = IntegerField(u'Entity pk', blank=True, null=True) # Saving the pk of the entity entity_ct = CTypeForeignKey(verbose_name=_(u'What'), null=True, blank=True) created = CreationDateTimeField(_(u'Creation date'), default=now) entity_changes = TextField(_(u'Entity changes'), default=_empty_dump) type = IntegerField( _(u'Type'), choices=USER_HISTORY_TYPE) # TODO: SmallPositiveInteger ? where = IntegerField( _(u'Where'), choices=USER_HISTORY_WHERE) # TODO: SmallPositiveInteger ? _entity = None class Meta: app_label = 'activesync' verbose_name = u'History' verbose_name_plural = u'History' def __str__(self): return u"<UserSynchronizationHistory user=%s, entity_repr=%s>" % ( self.user, self.entity_repr) def _get_entity(self): if self.entity_pk is None: return _entity = self._entity if _entity is not None: return _entity #TODO: refactor (remove this 'return') try: _entity = self._entity = CremeEntity.objects.get( pk=self.entity_pk).get_real_entity() except CremeEntity.DoesNotExist: pass return _entity def _set_entity(self, entity): self.entity_pk = entity.pk self.entity_repr = str(entity) self.entity_ct = entity.entity_type # self._entity = entity entity = property(_get_entity, _set_entity) del _get_entity, _set_entity @staticmethod def populate_entities(histories): # entities_pks = histories.values_list('entity_pk', flat=True) entities_pks = [history.entity_pk for history in histories] # entities = CremeEntity.objects.filter(pk__in=entities_pks) entities = CremeEntity.objects.filter( pk__in=set(entities_pks) ) #Forcing the retrieve for MySQL <= v5.1.49 which "doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery" CremeEntity.populate_real_entities(entities) entities_map = { entity.pk: entity.get_real_entity() for entity in entities } for hist in histories: hist._entity = entities_map.get(hist.entity_pk) #TODO: Optimize db queries def _get_changes(self): changes = pickle.loads(self.entity_changes.encode('utf-8')) get_for_id = ContentType.objects.get_for_id for k, v in changes.items(): if isinstance(v, dict): model_class = get_for_id(v['ct_id']).model_class() try: changes[k] = model_class._default_manager.get(pk=v['pk']) except model_class.DoesNotExist: changes[k] = _(u"This entity doesn't exist anymore") return changes def _set_changes(self, entity_changes): """ Set changes in self.entity_changes @params entity_changes has to be an iterable of (key, value) => [('a',1),...] or .items(), etc. if a value is None the key is deleted if a value is a django 'Model' it will be transformed into a dict {'ct_id': ContentType id, 'pk': Its pk} """ changes = self.changes get_for_model = ContentType.objects.get_for_model django_model = Model for k_change, v_change in entity_changes: if v_change is not None: if isinstance(v_change, django_model): v_change = { 'ct_id': get_for_model(v_change).id, 'pk': v_change.pk, } changes[k_change] = v_change # elif changes.has_key(k_change): # del changes[k_change] else: changes.pop(k_change, None) self.entity_changes = pickle.dumps(changes) changes = property(_get_changes, _set_changes) del _get_changes, _set_changes @staticmethod def _add(user, entity, where, type, entity_changes=None): ush = UserSynchronizationHistory(user=user, where=where, type=type) if entity_changes is not None: ush.changes = entity_changes if isinstance(entity, CremeEntity): ush.entity = entity else: repr, ct = entity ush.entity_repr = str(repr) ush.entity_ct = ct ush.save() return ush @staticmethod def add_create(user, entity, where): return UserSynchronizationHistory._add(user, entity, where, CREATE) @staticmethod def add_update(user, entity, where, entity_changes): return UserSynchronizationHistory._add(user, entity, where, UPDATE, entity_changes) @staticmethod def add_delete(user, entity, where): return UserSynchronizationHistory._add(user, entity, where, DELETE)
class Migration(migrations.Migration): dependencies = [ ('creme_core', '0059_v2_1__convert_old_filter_format'), ] operations = [ migrations.AddField( model_name='entityfilter', name='filter_type', field=models.PositiveSmallIntegerField( choices=[ (0, 'Credentials filter (internal use)'), (1, 'Regular filter (usable in list-view...'), ], default=1, editable=False, ), ), migrations.AddField( model_name='setcredentials', name='efilter', field=models.ForeignKey(editable=False, null=True, on_delete=deletion.PROTECT, to='creme_core.EntityFilter'), ), migrations.AlterField( model_name='setcredentials', name='ctype', field=CTypeForeignKey( blank=True, null=True, on_delete=deletion.CASCADE, to='contenttypes.ContentType', verbose_name='Apply to a specific type', ), ), migrations.AlterField( model_name='setcredentials', name='forbidden', field=models.BooleanField( choices=[ (False, 'The users are allowed to perform the selected actions'), (True, 'The users are NOT allowed to perform the selected actions' ), ], default=False, help_text= 'Notice that actions which are forbidden & allowed at the same time are considered as forbidden when final permissions are computed.', verbose_name='Allow or forbid?', ), ), migrations.AlterField( model_name='setcredentials', name='set_type', field=models.PositiveIntegerField( choices=[(1, 'All entities'), (2, "User's own entities"), (3, 'Filtered entities')], default=1, help_text= 'The choice «Filtered entities» allows to configure credentials based on values of fields or relationships for example.', verbose_name='Type of entities set', ), ), ]