def __init__(self, field, request, params, model, model_admin, field_path): self.other_model = get_model_from_relation(field) if remote_field(field) is not None and hasattr(remote_field(field), 'get_related_field'): self.rel_name = remote_field(field).get_related_field().name else: self.rel_name = self.other_model._meta.pk.name self.changed_lookup_kwarg = '%s__%s__inhierarchy' % (field_path, self.rel_name) super(TreeRelatedFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path) self.lookup_val = request.GET.get(self.changed_lookup_kwarg)
def choices(self, cl): # #### MPTT ADDITION START try: # EMPTY_CHANGELIST_VALUE has been removed in django 1.9 from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE except: EMPTY_CHANGELIST_VALUE = self.empty_value_display # #### MPTT ADDITION END yield { 'selected': self.lookup_val is None and not self.lookup_val_isnull, 'query_string': cl.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]), 'display': _('All'), } for pk_val, val, padding_style in self.lookup_choices: yield { 'selected': self.lookup_val == smart_text(pk_val), 'query_string': cl.get_query_string({ self.lookup_kwarg: pk_val, }, [self.lookup_kwarg_isnull]), 'display': val, # #### MPTT ADDITION START 'padding_style': padding_style, # #### MPTT ADDITION END } if (isinstance(self.field, ForeignObjectRel) and (self.field.field.null or isinstance(self.field.field, ManyToManyField)) or remote_field(self.field) is not None and (self.field.null or isinstance(self.field, ManyToManyField))): yield { 'selected': bool(self.lookup_val_isnull), 'query_string': cl.get_query_string({ self.lookup_kwarg_isnull: 'True', }, [self.lookup_kwarg]), 'display': EMPTY_CHANGELIST_VALUE, }
def _is_saved(self, using=None): if not self.pk or self._mpttfield('tree_id') is None: return False opts = self._meta if remote_field(opts.pk) is None: return True else: if not hasattr(self, '_mptt_saved'): manager = self.__class__._base_manager manager = manager.using(using) self._mptt_saved = manager.filter(pk=self.pk).exists() return self._mptt_saved
def add_related_count(self, queryset, rel_model, rel_field, count_attr, cumulative=False): """ Adds a related item count to a given ``QuerySet`` using its ``extra`` method, for a ``Model`` class which has a relation to this ``Manager``'s ``Model`` class. Arguments: ``rel_model`` A ``Model`` class which has a relation to this `Manager``'s ``Model`` class. ``rel_field`` The name of the field in ``rel_model`` which holds the relation. ``count_attr`` The name of an attribute which should be added to each item in this ``QuerySet``, containing a count of how many instances of ``rel_model`` are related to it through ``rel_field``. ``cumulative`` If ``True``, the count will be for each item and all of its descendants, otherwise it will be for each item itself. """ connection = self._get_connection() qn = connection.ops.quote_name meta = self.model._meta mptt_field = rel_model._meta.get_field(rel_field) if isinstance(mptt_field, ManyToManyField): if cumulative: subquery = CUMULATIVE_COUNT_SUBQUERY_M2M % { 'rel_table': qn(rel_model._meta.db_table), 'rel_pk': qn(rel_model._meta.pk.column), 'rel_m2m_table': qn(mptt_field.m2m_db_table()), 'rel_m2m_column': qn(mptt_field.m2m_column_name()), 'mptt_fk': qn(mptt_field.m2m_reverse_name()), 'mptt_table': qn(self.tree_model._meta.db_table), 'mptt_pk': qn(meta.pk.column), 'tree_id': qn(meta.get_field(self.tree_id_attr).column), 'left': qn(meta.get_field(self.left_attr).column), 'right': qn(meta.get_field(self.right_attr).column), } else: subquery = COUNT_SUBQUERY_M2M % { 'rel_table': qn(rel_model._meta.db_table), 'rel_pk': qn(rel_model._meta.pk.column), 'rel_m2m_table': qn(mptt_field.m2m_db_table()), 'rel_m2m_column': qn(mptt_field.m2m_column_name()), 'mptt_fk': qn(mptt_field.m2m_reverse_name()), 'mptt_table': qn(self.tree_model._meta.db_table), 'mptt_pk': qn(meta.pk.column), } else: if cumulative: subquery = CUMULATIVE_COUNT_SUBQUERY % { 'rel_table': qn(rel_model._meta.db_table), 'mptt_fk': qn(rel_model._meta.get_field(rel_field).column), 'mptt_table': qn(self.tree_model._meta.db_table), 'mptt_rel_to': qn(remote_field(mptt_field).field_name), 'tree_id': qn(meta.get_field(self.tree_id_attr).column), 'left': qn(meta.get_field(self.left_attr).column), 'right': qn(meta.get_field(self.right_attr).column), } else: subquery = COUNT_SUBQUERY % { 'rel_table': qn(rel_model._meta.db_table), 'mptt_fk': qn(rel_model._meta.get_field(rel_field).column), 'mptt_table': qn(self.tree_model._meta.db_table), 'mptt_rel_to': qn(remote_field(mptt_field).field_name), } return queryset.extra(select={count_attr: subquery})
def add_related_count(self, queryset, rel_model, rel_field, count_attr, cumulative=False): """ Adds a related item count to a given ``QuerySet`` using its ``extra`` method, for a ``Model`` class which has a relation to this ``Manager``'s ``Model`` class. Arguments: ``rel_model`` A ``Model`` class which has a relation to this `Manager``'s ``Model`` class. ``rel_field`` The name of the field in ``rel_model`` which holds the relation. ``count_attr`` The name of an attribute which should be added to each item in this ``QuerySet``, containing a count of how many instances of ``rel_model`` are related to it through ``rel_field``. ``cumulative`` If ``True``, the count will be for each item and all of its descendants, otherwise it will be for each item itself. """ connection = self._get_connection() qn = connection.ops.quote_name meta = self.model._meta mptt_field = rel_model._meta.get_field(rel_field) if isinstance(mptt_field, ManyToManyField): if cumulative: subquery = CUMULATIVE_COUNT_SUBQUERY_M2M % { "rel_table": qn(rel_model._meta.db_table), "rel_pk": qn(rel_model._meta.pk.column), "rel_m2m_table": qn(mptt_field.m2m_db_table()), "rel_m2m_column": qn(mptt_field.m2m_column_name()), "mptt_fk": qn(mptt_field.m2m_reverse_name()), "mptt_table": qn(self.tree_model._meta.db_table), "mptt_pk": qn(meta.pk.column), "tree_id": qn(meta.get_field(self.tree_id_attr).column), "left": qn(meta.get_field(self.left_attr).column), "right": qn(meta.get_field(self.right_attr).column), } else: subquery = COUNT_SUBQUERY_M2M % { "rel_table": qn(rel_model._meta.db_table), "rel_pk": qn(rel_model._meta.pk.column), "rel_m2m_table": qn(mptt_field.m2m_db_table()), "rel_m2m_column": qn(mptt_field.m2m_column_name()), "mptt_fk": qn(mptt_field.m2m_reverse_name()), "mptt_table": qn(self.tree_model._meta.db_table), "mptt_pk": qn(meta.pk.column), } else: if cumulative: subquery = CUMULATIVE_COUNT_SUBQUERY % { "rel_table": qn(rel_model._meta.db_table), "mptt_fk": qn(rel_model._meta.get_field(rel_field).column), "mptt_table": qn(self.tree_model._meta.db_table), "mptt_rel_to": qn(remote_field(mptt_field).field_name), "tree_id": qn(meta.get_field(self.tree_id_attr).column), "left": qn(meta.get_field(self.left_attr).column), "right": qn(meta.get_field(self.right_attr).column), } else: subquery = COUNT_SUBQUERY % { "rel_table": qn(rel_model._meta.db_table), "mptt_fk": qn(rel_model._meta.get_field(rel_field).column), "mptt_table": qn(self.tree_model._meta.db_table), "mptt_rel_to": qn(remote_field(mptt_field).field_name), } return queryset.extra(select={count_attr: subquery})
def mptt_items_for_result(cl, result, form): """ Generates the actual list of data. """ def link_in_col(is_first, field_name, cl): if cl.list_display_links is None: return False if is_first and not cl.list_display_links: return True return field_name in cl.list_display_links first = True pk = cl.lookup_opts.pk.attname # #### MPTT ADDITION START # figure out which field to indent mptt_indent_field = getattr(cl.model_admin, 'mptt_indent_field', None) if not mptt_indent_field: for field_name in cl.list_display: try: f = cl.lookup_opts.get_field(field_name) except models.FieldDoesNotExist: if (mptt_indent_field is None and field_name != 'action_checkbox'): mptt_indent_field = field_name else: # first model field, use this one mptt_indent_field = field_name break # figure out how much to indent mptt_level_indent = getattr(cl.model_admin, 'mptt_level_indent', MPTT_ADMIN_LEVEL_INDENT) # #### MPTT ADDITION END for field_index, field_name in enumerate(cl.list_display): # #### MPTT SUBSTITUTION START empty_value_display = get_empty_value_display(cl) # #### MPTT SUBSTITUTION END row_classes = [ 'field-%s' % _coerce_field_name(field_name, field_index) ] try: f, attr, value = lookup_field(field_name, result, cl.model_admin) except ObjectDoesNotExist: result_repr = empty_value_display else: empty_value_display = getattr(attr, 'empty_value_display', empty_value_display) if f is None or f.auto_created: if field_name == 'action_checkbox': row_classes = ['action-checkbox'] allow_tags = getattr(attr, 'allow_tags', False) boolean = getattr(attr, 'boolean', False) # #### MPTT SUBSTITUTION START try: # Changed in Django 1.9, now takes 3 arguments result_repr = display_for_value(value, empty_value_display, boolean) except TypeError: result_repr = display_for_value(value, boolean) # #### MPTT SUBSTITUTION END if allow_tags: warnings.warn( "Deprecated allow_tags attribute used on field {}. " "Use django.utils.safestring.format_html(), " "format_html_join(), or mark_safe() instead.".format( field_name), RemovedInDjango20Warning) result_repr = mark_safe(result_repr) if isinstance(value, (datetime.date, datetime.time)): row_classes.append('nowrap') else: # #### MPTT SUBSTITUTION START is_many_to_one = isinstance(remote_field(f), models.ManyToOneRel) if is_many_to_one: # #### MPTT SUBSTITUTION END field_val = getattr(result, f.name) if field_val is None: result_repr = empty_value_display else: result_repr = field_val else: # #### MPTT SUBSTITUTION START try: result_repr = display_for_field(value, f) except TypeError: # Changed in Django 1.9, now takes 3 arguments result_repr = display_for_field( value, f, empty_value_display) # #### MPTT SUBSTITUTION END if isinstance( f, (models.DateField, models.TimeField, models.ForeignKey)): row_classes.append('nowrap') if force_text(result_repr) == '': result_repr = mark_safe(' ') row_class = mark_safe(' class="%s"' % ' '.join(row_classes)) # #### MPTT ADDITION START if field_name == mptt_indent_field: level = getattr(result, result._mptt_meta.level_attr) padding_attr = mark_safe( ' style="padding-%s:%spx"' % ('right' if get_language_bidi() else 'left', 8 + mptt_level_indent * level)) else: padding_attr = '' # #### MPTT ADDITION END # If list_display_links not defined, add the link tag to the first field if link_in_col(first, field_name, cl): table_tag = 'th' if first else 'td' first = False # Display link to the result's change_view if the url exists, else # display just the result's representation. try: url = cl.url_for_result(result) except NoReverseMatch: link_or_text = result_repr else: url = add_preserved_filters( { 'preserved_filters': cl.preserved_filters, 'opts': cl.opts }, url) # Convert the pk to something that can be used in Javascript. # Problem cases are long ints (23L) and non-ASCII strings. if cl.to_field: attr = str(cl.to_field) else: attr = pk value = result.serializable_value(attr) if cl.is_popup: if django.VERSION < (1, 10): opener = format_html( ' onclick="opener.dismissRelatedLookupPopup(window, '{}'); return false;"', value) else: opener = format_html(' data-popup-opener="{}"', value) else: opener = '' link_or_text = format_html('<a href="{}"{}>{}</a>', url, opener, result_repr) # #### MPTT SUBSTITUTION START yield format_html('<{}{}{}>{}</{}>', table_tag, row_class, padding_attr, link_or_text, table_tag) # #### MPTT SUBSTITUTION END else: # By default the fields come from ModelAdmin.list_editable, but if we pull # the fields out of the form instead of list_editable custom admins # can provide fields on a per request basis if (form and field_name in form.fields and not (field_name == cl.model._meta.pk.name and form[cl.model._meta.pk.name].is_hidden)): bf = form[field_name] result_repr = mark_safe(force_text(bf.errors) + force_text(bf)) # #### MPTT SUBSTITUTION START yield format_html('<td{}{}>{}</td>', row_class, padding_attr, result_repr) # #### MPTT SUBSTITUTION END if form and not form[cl.model._meta.pk.name].is_hidden: yield format_html('<td>{}</td>', force_text(form[cl.model._meta.pk.name]))
def mptt_items_for_result(cl, result, form): """ Generates the actual list of data. """ def link_in_col(is_first, field_name, cl): if cl.list_display_links is None: return False if is_first and not cl.list_display_links: return True return field_name in cl.list_display_links first = True pk = cl.lookup_opts.pk.attname # #### MPTT ADDITION START # figure out which field to indent mptt_indent_field = getattr(cl.model_admin, 'mptt_indent_field', None) if not mptt_indent_field: for field_name in cl.list_display: try: f = cl.lookup_opts.get_field(field_name) except models.FieldDoesNotExist: if (mptt_indent_field is None and field_name != 'action_checkbox'): mptt_indent_field = field_name else: # first model field, use this one mptt_indent_field = field_name break # figure out how much to indent mptt_level_indent = getattr(cl.model_admin, 'mptt_level_indent', MPTT_ADMIN_LEVEL_INDENT) # #### MPTT ADDITION END for field_index, field_name in enumerate(cl.list_display): # #### MPTT SUBSTITUTION START empty_value_display = get_empty_value_display(cl) # #### MPTT SUBSTITUTION END row_classes = ['field-%s' % _coerce_field_name(field_name, field_index)] try: f, attr, value = lookup_field(field_name, result, cl.model_admin) except ObjectDoesNotExist: result_repr = empty_value_display else: empty_value_display = getattr(attr, 'empty_value_display', empty_value_display) if f is None or f.auto_created: if field_name == 'action_checkbox': row_classes = ['action-checkbox'] allow_tags = getattr(attr, 'allow_tags', False) boolean = getattr(attr, 'boolean', False) # #### MPTT SUBSTITUTION START try: # Changed in Django 1.9, now takes 3 arguments result_repr = display_for_value( value, empty_value_display, boolean) except TypeError: result_repr = display_for_value(value, boolean) # #### MPTT SUBSTITUTION END if allow_tags: warnings.warn( "Deprecated allow_tags attribute used on field {}. " "Use django.utils.safestring.format_html(), " "format_html_join(), or mark_safe() instead.".format(field_name), RemovedInDjango20Warning ) result_repr = mark_safe(result_repr) if isinstance(value, (datetime.date, datetime.time)): row_classes.append('nowrap') else: # #### MPTT SUBSTITUTION START is_many_to_one = isinstance(remote_field(f), models.ManyToOneRel) if is_many_to_one: # #### MPTT SUBSTITUTION END field_val = getattr(result, f.name) if field_val is None: result_repr = empty_value_display else: result_repr = field_val else: # #### MPTT SUBSTITUTION START try: result_repr = display_for_field(value, f) except TypeError: # Changed in Django 1.9, now takes 3 arguments result_repr = display_for_field( value, f, empty_value_display) # #### MPTT SUBSTITUTION END if isinstance(f, (models.DateField, models.TimeField, models.ForeignKey)): row_classes.append('nowrap') if force_text(result_repr) == '': result_repr = mark_safe(' ') row_class = mark_safe(' class="%s"' % ' '.join(row_classes)) # #### MPTT ADDITION START if field_name == mptt_indent_field: level = getattr(result, result._mptt_meta.level_attr) padding_attr = mark_safe(' style="padding-%s:%spx"' % ( 'right' if get_language_bidi() else 'left', 8 + mptt_level_indent * level)) else: padding_attr = '' # #### MPTT ADDITION END # If list_display_links not defined, add the link tag to the first field if link_in_col(first, field_name, cl): table_tag = 'th' if first else 'td' first = False # Display link to the result's change_view if the url exists, else # display just the result's representation. try: url = cl.url_for_result(result) except NoReverseMatch: link_or_text = result_repr else: url = add_preserved_filters({'preserved_filters': cl.preserved_filters, 'opts': cl.opts}, url) # Convert the pk to something that can be used in Javascript. # Problem cases are long ints (23L) and non-ASCII strings. if cl.to_field: attr = str(cl.to_field) else: attr = pk value = result.serializable_value(attr) link_or_text = format_html( '<a href="{}"{}>{}</a>', url, format_html( ' data-popup-opener="{}"', value ) if cl.is_popup else '', result_repr) # #### MPTT SUBSTITUTION START yield format_html('<{}{}{}>{}</{}>', table_tag, row_class, padding_attr, link_or_text, table_tag) # #### MPTT SUBSTITUTION END else: # By default the fields come from ModelAdmin.list_editable, but if we pull # the fields out of the form instead of list_editable custom admins # can provide fields on a per request basis if (form and field_name in form.fields and not ( field_name == cl.model._meta.pk.name and form[cl.model._meta.pk.name].is_hidden)): bf = form[field_name] result_repr = mark_safe(force_text(bf.errors) + force_text(bf)) # #### MPTT SUBSTITUTION START yield format_html('<td{}{}>{}</td>', row_class, padding_attr, result_repr) # #### MPTT SUBSTITUTION END if form and not form[cl.model._meta.pk.name].is_hidden: yield format_html('<td>{}</td>', force_text(form[cl.model._meta.pk.name]))