Django queryset скрывает значение объекта

У меня есть следующая (упрощенная) модель:

class Order(models.Model):
    is_anonymized = models.BooleanField(default=False)
    billing_address = models.ForeignKey('order.BillingAddress', null=True, blank=True)

Я хочу скрыть адрес_расчета для объектов, где клиент выбрал это, установив is_anonymized=True.

Моим лучшим подходом до сих пор было сделать это в init:

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.is_anonymized:
            self.billing_address = None
            self.billing_address_id = None

Это прекрасно работает в админке, НО...

везде в коде есть select_related-QuerySets для Orders:

    queryset = Order._default_manager.select_related('billing_address')

Везде, где используется select_related-querysets, случайно показывается billing_address. В других местах (например, в админке) его нет.

Как я могу обеспечить удаление billing_address везде для объектов с is_anonymized = True?

Я думал о перезаписи набора запросов в менеджере, но не смог перезаписать поле billing_address по условию.

Использование шаблона getter-setter не является хорошим решением, потому что он ломает админку в нескольких местах (есть много атрибутов для маскировки, например, billing_address).

Начну с того, что я не понимаю, зачем вам скрывать информацию от администратора вашей системы. Если только у вас не сложная рабочая среда, где только DBA имеет доступ к такой информации, я честно не вижу в этом смысла.

Чтобы ответить на ваш вопрос...

Чтобы скрыть информацию на странице администратора, один из вариантов - отключить все ссылки и заменить HTML на ссылку редактирования, когда значение is_anonymized равно False: (адаптировано из ответ_1 и ответ_2)

admin.py:

from django.utils.html import format_html

class OrderAdmin(admin.ModelAdmin):
    list_display = ['anonymous_address']

    def anonymous_address(self, obj):
        if not obj.is_anonymized:
            return format_html(u'<a href="/admin/app/order/{}/change/">{}</a>', obj.id, obj.billing_address.address)
        else:
            return ("%s" % ('anonymous'))
    
    def __init__(self, *args, **kwargs):
        super(OrderAdmin, self).__init__(*args, **kwargs)
        self.list_display_links = None

admin.site.register(Order, OrderAdmin)

Обратите внимание, что при таком решении у администратора все еще есть доступ к BillingAddress модели, если вы зарегистрировали ее на сайте администратора. В этом случае также необходимо будет переопределить это.

В ваших запросах вы можете агрегировать значения с помощью условных выражений:

views.py:

from core.models import Order
from django.db.models import When, Case

def anonymous_address(request):
    orders = Order.objects.annotate(anonymised_address=Case(
        When(is_anonymized=True, then=None),
        When(is_anonymized=False, then='billing_address'),
    )).values('is_anonymized', 'anonymised_address')
    
    context = {'orders': orders}
    return render(request, 'anonymous_address.html', context)

anonymous_address.html:


{% block content %}
    {% for order in orders %}
        Should be anonymous: {{order.is_anonymized}} <br>
        Address: {{order.anonymised_address}}
        <hr>
    {% endfor %}
{% endblock content %}

Вместо того, чтобы иметь этот длинный запрос в каждом представлении, можно заменить его на пользовательский менеджер:

models.py:

class AnonymousOrdersManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().annotate(anonymised_address=Case(
                    When(is_anonymized=True, then=None),
                    When(is_anonymized=False, then='billing_address'),
                )).values('is_anonymized', 'anonymised_address')

class Order(models.Model):
    is_anonymized = models.BooleanField(default=False)
    billing_address = models.ForeignKey(BillingAdress, null=True, blank=True, on_delete=models.CASCADE)

    objects = models.Manager()
    anonymous_orders = AnonymousOrdersManager()

views.py:

def anonymous_address(request):
    orders = Order.anonymous_orders.all()
    
    context = {'orders': orders}
    return render(request, 'anonymous_address.html', context)
Вернуться на верх