from enum import Enum, unique from django.db.models.fields import NOT_PROVIDED from django.db.models import Model, ForeignKey from django.db.models.query import QuerySet from ..prettyprinter import ( MULTILINE_STRATEGY_HANG, build_fncall, pretty_call_alt, pretty_python_value, register_pretty, comment_doc, trailing_comment ) from ..utils import find QUERYSET_OUTPUT_SIZE = 20 @unique class ModelVerbosity(Enum): UNSET = 1 MINIMAL = 2 SHORT = 3 FULL = 4 def inc(value): return value class dec(object): __slots__ = ('value', ) def __init__(self, value): self.value = value def __lt__(self, other): assert isinstance(other, dec) return self.value > other.value def __gt__(self, other): assert isinstance(other, dec) return self.value < other.value def __eq__(self, other): assert isinstance(other, dec) return self.value == other.value def __le__(self, other): assert isinstance(other, dec) return self.value >= other.value def __ge__(self, other): assert isinstance(other, dec) return self.value <= other.value __hash__ = None def field_sort_key(field): return ( dec(field.primary_key), dec(field.unique), inc(field.null), inc(field.blank), dec(field.default is NOT_PROVIDED), inc(field.name), ) def pretty_base_model(instance, ctx): verbosity = ctx.get(ModelVerbosity) model = type(instance) if verbosity == ModelVerbosity.MINIMAL: fields = [find(lambda field: field.primary_key, model._meta.fields)] elif verbosity == ModelVerbosity.SHORT: fields = sorted( ( field for field in model._meta.fields if field.primary_key or not field.null and field.unique ), key=field_sort_key ) else: fields = sorted(model._meta.fields, key=field_sort_key) attrs = [] value_ctx = ( ctx .nested_call() .use_multiline_strategy(MULTILINE_STRATEGY_HANG) ) null_fieldnames = [] blank_fieldnames = [] default_fieldnames = [] for field in fields: if isinstance(field, ForeignKey): fk_value = getattr(instance, field.attname) if fk_value is not None: related_field = field.target_field related_model = related_field.model attrs.append(( field.name, pretty_call_alt( ctx, related_model, [(related_field.name, fk_value)] ) )) else: null_fieldnames.append(field.attname) else: value = getattr(instance, field.name) if field.default is not NOT_PROVIDED: if callable(field.default): default_value = field.default() else: default_value = field.default if value == default_value: default_fieldnames.append(field.attname) continue if field.null and value is None: null_fieldnames.append(field.attname) continue if field.blank and value in field.empty_values: blank_fieldnames.append(field.attname) continue kw = field.name vdoc = pretty_python_value(value, value_ctx) if field.choices: choices = tuple(field.choices) ungrouped_choices = ( (_value, _display) for value, display in choices for _value, _display in ( display if isinstance(display, tuple) else [(value, display)]) ) value__display = find( lambda value__display: value__display[0] == value, ungrouped_choices, ) try: _, display = value__display except ValueError: display = None if display: vdoc = comment_doc( vdoc, display ) attrs.append((kw, vdoc)) commentstr = ( ( "Null fields: {}\n".format( ', '.join(null_fieldnames) ) if null_fieldnames else '' ) + ( "Blank fields: {}\n".format( ', '.join(blank_fieldnames) ) if blank_fieldnames else '' ) + ( "Default value fields: {}\n".format( ', '.join(default_fieldnames) ) if default_fieldnames else '' ) ) return build_fncall( ctx, model, kwargdocs=attrs, trailing_comment=commentstr or None, ) def pretty_queryset(queryset, ctx): qs_cls = type(queryset) instances = list(queryset[:QUERYSET_OUTPUT_SIZE + 1]) if len(instances) > QUERYSET_OUTPUT_SIZE: truncated = True instances = instances[:-1] else: truncated = False element_ctx = ctx.assoc(ModelVerbosity, ModelVerbosity.SHORT) arg = ( trailing_comment( instances, '...remaining elements truncated' ) if truncated else instances ) return pretty_call_alt( element_ctx, qs_cls, args=(arg, ) ) def install(): register_pretty(Model)(pretty_base_model) register_pretty(QuerySet)(pretty_queryset)