Извините за длину этого вопроса, я не мог сделать его короче и по-прежнему значимым.
У нас очень простое приложение с двумя простыми моделями Company
и Building
, между которыми существует отношение «многие ко многим». У каждого есть атрибут restricted
. User
- это обычный класс Django User
, за исключением того, что мы добавили атрибут show
.
# models.py
class User(AbstractUser):
show = models.BooleanField(default=True)
class Company(models.Model):
name = models.CharField(max_length=100)
restricted = models.BooleanField(default=False)
class Building(models.Model):
name = models.CharField(max_length=100)
restricted = models.BooleanField(default=False)
companies = models.ManyToManyField(Company, related_name='buildings')
Представления являются обычными наборами представлений Django REST Framework, а сериализаторы максимально просты:
# views.py
class CompanyViewSet(ModelViewSet):
queryset = Company.objects.all()
serializer_class = CompanySerializer
class BuildingViewSet(ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
# serializers.py
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = '__all__'
class BuildingSerializer(serializers.ModelSerializer):
class Meta:
model = Building
fields = '__all__'
Теперь мы хотим реализовать это поведение : если user.show
равен False
, пользователь должен не иметь возможность видеть (в представлениях) { {X2}} Company
и Building
.
Другими словами, если john
является User
и john.show is False
, john
может видеть (во взглядах) normal_company
и normal_building
, но < strong> not restricted_company
или restricted_building
.
Чтобы добиться этого, мы не хотим редактировать представления / сериализаторы , если это возможно, потому что их много (это упрощенная версия реального проекта, который намного больше).
Моя команда думала об использовании промежуточного программного обеспечения. Мы попытались динамически изменить Company.objects
и Building.objects
:
# middleware.py
class FilterMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = get_user() # Get the user somehow.
if not user.show:
# Replace objects.
for model in (Company, Building):
model.objects = model.objects.filter(restricted=False)
response = self.get_response(request)
return response
Но это, по какой-то неизвестной причине, не работает: john
все еще могут видеть компании с ограниченным доступом. Затем мы попытались динамически обновить метод django.db.models.Manager.get_queryset
:
# middleware.py
class FilterMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = get_user()
if not user.show:
models.Manager.get_queryset = get_restricted_queryset
response = self.get_response(request)
return response
def get_restricted_queryset(self, *args, **kwargs):
# Conditions for the filter later.
hiding_conditions = {
"Company": Q(restricted=True),
"Building": Q(companies__restricted=True) | Q(restricted=True),
}
model_name = self.model.__name__
if model_name in hiding_conditions:
# We must filter the model out, so apply the hiding conditions.
hiding_condition = hiding_conditions[model_name]
return self._queryset_class(
model=self.model, using=self._db, hints=self._hints
).exclude(hiding_condition)
else:
return self._queryset_class(model=self.model, using=self._db, hints=self._hints)
Но это не работает - и это странно: когда я выбираю компании, это на самом деле только модель User
, которая вызывается get_queryset
, поэтому get_restricted_queryset
не имеет никакого эффекта
Теперь мы действительно застряли. У кого-нибудь есть идея, которая может нам помочь? Или просто промежуточное программное обеспечение не должно делать такие вещи?
1 ответ
Вам не нужно промежуточное программное обеспечение (поскольку промежуточное программное обеспечение работает только с запросами и ответами, которые на уровне абстракции ниже QuerySets). Вы можете сделать это в DRF, используя пользовательский FilterBackend, например: Обновление: фильтруйте и вложенные компании!
from rest_framework import filters
class IsRestrictedFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
if request.user and user.is_authenticated and not user.show:
if queryset.model and queryset.model in [Company, Building]:
queryset = queryset.filter(restricted=False)
if queryset.model == Building:
return queryset.filter(companies__restricted=False)
return queryset
Затем вы добавляете этот фильтр в свои настройки:
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['yourapp.filter_backends.IsRestrictedFilterBackend']
}
... или вы можете использовать его на основе ViewSet:
class BuildingViewSet(ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
filter_backends = [yourapp.filter_backends.IsRestrictedFilterBackend]
Похожие вопросы
Новые вопросы
python
Python - это многопарадигмальный, динамически типизированный, многоцелевой язык программирования. Он разработан для быстрого изучения, понимания и использования, а также для обеспечения чистого и единообразного синтаксиса. Обратите внимание, что Python 2 официально не поддерживается с 01.01.2020. Тем не менее, для вопросов о Python, связанных с версией, добавьте тег [python-2.7] или [python-3.x]. При использовании варианта Python (например, Jython, PyPy) или библиотеки (например, Pandas и NumPy) включите его в теги.