class FieldsConfigsBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('creme_config', 'fields_configs') dependencies = (FieldsConfig, ) page_size = _PAGE_SIZE verbose_name = 'Fields configuration' template_name = 'creme_config/bricks/fields-configs.html' permission = None # NB: used by the view creme_core.views.bricks.reload_basic() configurable = False def detailview_display(self, context): # TODO: exclude CTs that user cannot see ? (should probably done everywhere in creme_config...) fconfigs = [*FieldsConfig.objects.all()] sort_key = collator.sort_key fconfigs.sort(key=lambda fconf: sort_key(str(fconf.content_type))) used_models = {fconf.content_type.model_class() for fconf in fconfigs} btc = self.get_template_context( context, fconfigs, display_add_button=any( model not in used_models for model in filter( FieldsConfig.is_model_valid, apps.get_models())), ) for fconf in btc['page'].object_list: vnames = [str(f.verbose_name) for f in fconf.hidden_fields] vnames.sort(key=sort_key) fconf.fields_vnames = vnames return self._render(btc)
class CreditNotesBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('billing', 'credit_notes') dependencies = (Relation, CreditNote) relation_type_deps = (constants.REL_OBJ_CREDIT_NOTE_APPLIED, ) verbose_name = _(u'Related Credit Notes') template_name = 'billing/bricks/credit-notes.html' target_ctypes = ( Invoice, SalesOrder, Quote, ) def detailview_display(self, context): billing_document = context['object'] is_hidden = context['fields_configs'].get_4_model( CreditNote).is_fieldname_hidden return self._render( self.get_template_context( context, billing_document.get_credit_notes(), rtype_id=self.relation_type_deps[0], add_title=_(u'Create a credit note'), hidden_fields={ fname for fname in ('issuing_date', 'expiration_date', 'comment') if is_hidden(fname) }, ))
class SearchConfigBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('creme_config', 'searchconfig') dependencies = (SearchConfigItem, ) verbose_name = 'Search configuration' template_name = 'creme_config/bricks/search-config.html' order_by = 'content_type' # TODO _ConfigAdminBlock => Mixin page_size = _PAGE_SIZE * 2 # Only one brick permission = None # NB: used by the view creme_core.views.blocks.reload_basic() configurable = False def detailview_display(self, context): # NB: we wrap the ContentType instances instead of store extra data in # them because teh instances are stored in a global cache, so we do # not want to mutate them. class _ContentTypeWrapper: # TODO: move from here ? __slots__ = ('ctype', 'sc_items') def __init__(self, ctype): self.ctype = ctype self.sc_items = () ctypes = [ _ContentTypeWrapper(ctype) for ctype in creme_entity_content_types() ] sort_key = collator.sort_key ctypes.sort(key=lambda ctw: sort_key(str(ctw.ctype))) btc = self.get_template_context( context, ctypes, # NB: '+ 2' is for default config + super-users config. max_conf_count=UserRole.objects.count() + 2, ) ctypes_wrappers = btc['page'].object_list sci_map = defaultdict(list) for sci in SearchConfigItem.objects \ .filter(content_type__in=[ctw.ctype for ctw in ctypes_wrappers])\ .select_related('role'): sci_map[sci.content_type_id].append(sci) superusers_label = gettext('Superuser') for ctw in ctypes_wrappers: ctype = ctw.ctype ctw.sc_items = sc_items = sci_map.get(ctype.id) or [] sc_items.sort(key=lambda sci: sort_key( str(sci.role) if sci.role else superusers_label if sci.superuser else '')) if not sc_items or not sc_items[ 0].is_default: # No default config -> we build it SearchConfigItem.objects.create(content_type=ctype) return self._render(btc)
class CallersBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('cti', 'callers') verbose_name = _('Potential callers') template_name = 'cti/bricks/callers.html' configurable = False page_size = 128 caller_models: Sequence[Type[CremeEntity]] = ( Contact, Organisation, ) def detailview_display(self, context): # Ensure that it will crash if we try to load it from a classic load view number = context['number'] user = context['user'] filter_viewable = EntityCredentials.filter fconfigs = FieldsConfig.objects.get_for_models(self.caller_models) all_fields_hidden = True callers = [] for model in self.caller_models: is_hidden = fconfigs[model].is_field_hidden queries = [ Q(**{field.name: number}) for field in model._meta.fields if isinstance(field, PhoneField) and not is_hidden(field) ] if queries: all_fields_hidden = False callers.extend( filter_viewable( user, model.objects.exclude(is_deleted=True).filter( reduce(or_, queries)), )) if all_fields_hidden: raise ConflictError( gettext( 'All phone fields are hidden ; please contact your administrator.' )) can_create = user.has_perm_to_create return self._render( self.get_template_context( context, objects=callers, can_create_contact=can_create(Contact), contact_creation_label=Contact.creation_label, can_create_orga=can_create(Organisation), orga_creation_label=Organisation.creation_label, can_create_activity=can_create(Activity), ))
class RelatedOpportunitiesBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('commercial', 'opportunities') dependencies = (Relation, Opportunity) relation_type_deps = (REL_OBJ_COMPLETE_GOAL, ) verbose_name = _('Opportunities related to a Commercial Action') template_name = 'commercial/bricks/opportunities.html' target_ctypes = (Act, ) def detailview_display(self, context): act = context['object'] return self._render( self.get_template_context( context, act.get_related_opportunities(), predicate_id=REL_OBJ_COMPLETE_GOAL, ))
class BrickDetailviewLocationsBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('creme_config', 'blocks_dv_locations') dependencies = (BrickDetailviewLocation, ) page_size = _PAGE_SIZE - 1 # '-1' because there is always the line for default config on each page verbose_name = 'Blocks locations on detailviews' template_name = 'creme_config/bricks/bricklocations-detailviews.html' permission = None # NB: used by the view creme_core.views.blocks.reload_basic configurable = False brick_registry = brick_registry def detailview_display(self, context): # NB: we wrap the ContentType instances instead of store extra data in # them because the instances are stored in a global cache, so we do # not want to mutate them. class _ContentTypeWrapper: # TODO: move from here ? __slots__ = ('ctype', 'locations_info', 'default_count') def __init__(self, ctype): self.ctype = ctype self.default_count = 0 self.locations_info = ( ) # List of tuples (role_arg, role_label, block_count) # with role_arg == role.id or 'superuser' # TODO: factorise with SearchConfigBlock ? # TODO: factorise with CustomBlockConfigItemCreateForm , add a method in block_registry ? get_ct = ContentType.objects.get_for_model is_invalid = self.brick_registry.is_model_invalid ctypes = [ _ContentTypeWrapper(get_ct(model)) for model in creme_registry.iter_entity_models() if not is_invalid(model) ] sort_key = collator.sort_key ctypes.sort(key=lambda ctw: sort_key(str(ctw.ctype))) btc = self.get_template_context( context, ctypes, max_conf_count=UserRole.objects.count() + 1, # NB: '+ 1' is for super-users config. ) ctypes_wrappers = btc['page'].object_list brick_counts = defaultdict( lambda: defaultdict(int) ) # brick_counts[content_type.id][(role_id, superuser)] -> count role_ids = set() for bdl in BrickDetailviewLocation.objects \ .filter(content_type__in=[ctw.ctype for ctw in ctypes_wrappers])\ .exclude(zone=BrickDetailviewLocation.HAT): if bdl.brick_id: # Do not count the 'place-holder' (empty block IDs which mean "no-block for this zone") role_id = bdl.role_id brick_counts[bdl.content_type_id][(role_id, bdl.superuser)] += 1 role_ids.add(role_id) role_names = dict( UserRole.objects.filter(id__in=role_ids).values_list('id', 'name')) superusers_label = gettext('Superuser') # TODO: cached_lazy_gettext for ctw in ctypes_wrappers: count_per_role = brick_counts[ctw.ctype.id] ctw.default_count = count_per_role.pop((None, False), 0) ctw.locations_info = locations_info = [] for (role_id, superuser), block_count in count_per_role.items(): if superuser: role_arg = 'superuser' role_label = superusers_label else: role_arg = role_id role_label = role_names[role_id] locations_info.append((role_arg, role_label, block_count)) locations_info.sort( key=lambda t: sort_key(t[1])) # Sort by role label btc['default_count'] = BrickDetailviewLocation.objects.filter( content_type=None, role=None, superuser=False, ).count() return self._render(btc)
class NeglectedOrganisationsBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('persons', 'neglected_orgas') verbose_name = _('Neglected organisations') description = _( 'Displays customers/prospects organisations (for the Organisations managed by Creme) ' 'which have no Activity in the future. Expected Activities are related to:\n' '- The Organisations with a relationship «is subject of the activity» or ' '«related to the activity»\n' '- The managers & employees with a relationship «participates to the activity» ' '(plus the above ones)') dependencies = (Activity, ) template_name = 'persons/bricks/neglected-organisations.html' _RTYPE_IDS_CUSTOMERS = ( constants.REL_SUB_CUSTOMER_SUPPLIER, constants.REL_SUB_PROSPECT, ) _RTYPE_IDS_ORGA_N_ACT = ( activities_constants.REL_SUB_ACTIVITY_SUBJECT, activities_constants.REL_SUB_LINKED_2_ACTIVITY, ) _RTYPE_IDS_EMPLOYEES = ( constants.REL_SUB_MANAGES, constants.REL_SUB_EMPLOYED_BY, ) _RTYPE_IDS_CONTACT_N_ACT = ( activities_constants.REL_SUB_PART_2_ACTIVITY, activities_constants.REL_SUB_ACTIVITY_SUBJECT, activities_constants.REL_SUB_LINKED_2_ACTIVITY, ) def _get_neglected(self, now): user_contacts = Contact.objects.filter( is_user__isnull=False, ).values_list('id', flat=True) future_activities = [ *Activity.objects.filter( start__gte=now, relations__type=activities_constants. REL_OBJ_PART_2_ACTIVITY, relations__object_entity__in=user_contacts, ).values_list('id', flat=True), ] neglected_orgas_qs = Organisation.objects.filter( is_deleted=False, relations__type__in=self._RTYPE_IDS_CUSTOMERS, relations__object_entity__in=Organisation.objects. filter_managed_by_creme(), ).exclude(relations__type=constants.REL_SUB_INACTIVE).distinct() if not future_activities: # No need to retrieve it & transform into a list (good idea ??) return neglected_orgas_qs neglected_orgas = [ *neglected_orgas_qs.exclude( relations__object_entity__in=future_activities, relations__type__in=self._RTYPE_IDS_ORGA_N_ACT, ), ] if neglected_orgas: linked_people_map = dict( Relation.objects.filter( type__in=self._RTYPE_IDS_EMPLOYEES, object_entity__in=[o.id for o in neglected_orgas], ).values_list('subject_entity_id', 'object_entity_id'), ) activity_links = Relation.objects.filter( type__in=self._RTYPE_IDS_CONTACT_N_ACT, subject_entity__in=linked_people_map.keys(), object_entity__in=future_activities, ) # 'True' means 'neglected' neglected_map = {orga.id: True for orga in neglected_orgas} for rel in activity_links: neglected_map[linked_people_map[ rel.subject_entity_id]] = False neglected_orgas = [ orga for orga in neglected_orgas if neglected_map[orga.id] ] return neglected_orgas def home_display(self, context): # We do not check the 'persons' permission, because it's only # statistics for people who cannot see Organisations. return self._render( self.get_template_context( context, self._get_neglected(context['today']), ))
class EntityFiltersBrick(PaginatedBrick): id_ = PaginatedBrick.generate_id('creme_config', 'entity_filters') verbose_name = 'All entity filters' dependencies = (EntityFilter, ) page_size = _PAGE_SIZE template_name = 'creme_config/bricks/entity-filters.html' configurable = False def detailview_display(self, context): # NB: we wrap the ContentType instances instead of store extra data in # them because the instances are stored in a global cache, so we do # not want to mutate them. class _ContentTypeWrapper: __slots__ = ('ctype', 'all_users_filters', 'owned_filters') def __init__(this, ctype): this.ctype = ctype this.all_users_filters = () this.owned_filters = () # TODO: factorise with SearchConfigBrick ? get_ct = ContentType.objects.get_for_model user = context['user'] has_perm = user.has_perm_to_access ctypes = [ _ContentTypeWrapper(get_ct(model)) for model in creme_registry.iter_entity_models() if has_perm(model._meta.app_label) ] sort_key = collator.sort_key ctypes.sort(key=lambda ctw: sort_key(str(ctw.ctype))) btc = self.get_template_context(context, ctypes) ctypes_wrappers = btc['page'].object_list # NB: efilters[content_type.id][user.id] -> List[EntityFilter] efilters = defaultdict(lambda: defaultdict(list)) user_ids = set() for efilter in EntityFilter.objects.filter( filter_type=EF_USER, entity_type__in=[ctw.ctype for ctw in ctypes_wrappers], ): # TODO: templatetags instead ? efilter.edition_perm = efilter.can_edit(user)[0] efilter.deletion_perm = efilter.can_delete(user)[0] user_id = efilter.user_id efilters[efilter.entity_type_id][user_id].append(efilter) user_ids.add(user_id) users = get_user_model().objects.in_bulk(user_ids) def efilter_key(efilter): return sort_key(efilter.name) for ctw in ctypes_wrappers: ctype_efilters_per_users = efilters[ctw.ctype.id] all_users_filters = ctype_efilters_per_users.pop(None, None) or [] all_users_filters.sort(key=efilter_key) ctw.all_users_filters = all_users_filters ctw.owned_filters = [( str(users[user_id]), sorted(user_efilters, key=efilter_key), ) for user_id, user_efilters in ctype_efilters_per_users.items()] return self._render(btc)