Problem with working in admin panel

Hey!
I need to use the RLS features in the django admin panel, so that it would restrict the view for the users with is_staff, but it doesn’t seem to work.

In the code below i tried to test the capabilities of this module so that it would only show the ConferencePresentation object with the format_event being equal to Очный

the model i’ve used:

class ConferencePresentation(RLSModel):
    STATUS_CHOICES = [
        ("Международная", "Международная"),
        ("Всероссийская", "Всероссийская"),
        ("Региональная", "Региональная"),
    ]
    FORMAT_EVENT_CHOICES = [
        ("Очный", "Очный"),
        ("Онлайн", "Онлайн"),
    ]
    FORMAT_PARTICIPATION_CHOICES = [
        ("Очный", "Очный"),
        ("Онлайн", "Онлайн"),
        ("Стендовый", "Стендовый"),
    ]
    PUBLICATION_TYPE_CHOICES = [
        ("oral", "Презентация"),
        ("conference", "Конференция"),
    ]

    id: AutoField = AutoField(primary_key=True)
    author = GenericRelation(Author)
    conference_name: CharField = CharField(max_length=255, verbose_name="Название")
    place: CharField = CharField(max_length=255, verbose_name="Место проведения")
    dates: DateRangeField = DateRangeField(verbose_name="Даты проведения")
    single_date: DateField = DateField(null=True, blank=True)
    format_event: CharField = CharField(
        max_length=20, choices=FORMAT_EVENT_CHOICES, verbose_name="Формат проведения"
    )
    format_participation: CharField = CharField(
        max_length=20,
        choices=FORMAT_PARTICIPATION_CHOICES,
        verbose_name="Формат участия",
    )
    publication_type: CharField = CharField(
        max_length=20,
        choices=PUBLICATION_TYPE_CHOICES,
        verbose_name="Формат публикации",
    )
    status: CharField = CharField(
        max_length=20,
        choices=STATUS_CHOICES,
        blank=True,
        null=True,
        verbose_name="Статус",
    )
    approx_count_participants = Uint2Field(
        blank=True, null=True, verbose_name="Приблизительное количество участников"
    )
    url: models.URLField = models.URLField(
        max_length=255, blank=True, null=True, verbose_name="Ссылка на мероприятие"
    )

    def save(self, *args, **kwargs):
        if self.dates and self.dates.lower and self.dates.upper:
            if self.dates.lower == self.dates.upper:
                self.single_date = self.dates.lower

        super().save(*args, **kwargs)

    class Meta:
        # db_table = 'schema_public.conference_presentations'
        verbose_name = "Конференция / Выступление"
        verbose_name_plural = "Конференции / Выступления"
        constraints = [
            models.CheckConstraint(
                check=Q(
                    status__in=["Международная", "Всероссийская", "Региональная"]
                ),
                name="cns_conference_presentations",
            ),
            models.CheckConstraint(
                check=Q(format_event__in=["Очный", "Онлайн"]),
                name="cns_conference_presentations_0",
            ),
            models.CheckConstraint(
                check=Q(
                    format_participation__in=["Очный", "Онлайн", "Стендовый"]
                ),
                name="cns_conference_presentations_1",
            ),
            models.CheckConstraint(
                check=Q(publication_type__in=["oral", "conference"]),
                name="cns_conference_presentations_2",
            ),
        ]
        rls_policies = [
            ModelPolicy('access_policy', filters=Q(format_event="Очный"))
        ]

Settings:

INSTALLED_APPS = [
    "polymorphic",
    "users",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "core",
    "publications",
    "conferences_presentations",
    "reviews",
    "organizational_work",
    "scientific_guidance",
    "teaching_activities",
    "field_study",
    "expertise",
    "admin_autoregister",
    "django_jsonform",
    "django_rls",
]

ADMIN_AUTOREGISTER_EXCLUDE_INLINES = True

AUTH_USER_MODEL = "users.CustomUser"

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django_rls.middleware.RLSContextMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

DJANGO_RLS = {
    'AUTO_ENABLE_RLS': True
}

DATABASES = {
    "default": {
        "ENGINE": "django_rls.backends.postgresql" # using that insted of the default because of warning message `Database backend postgresql does not support RLS. Use 'django_rls.backends.postgresql' as your database ENGINE.`
        "NAME": getenv("DATABASE_NAME", "polls"),
        "USER": getenv("DATABASE_USERNAME", "myprojectuser"),
        "PASSWORD": getenv("DATABASE_PASSWORD", "password"),
        "HOST": getenv("DATABASE_HOST", "127.0.0.1"),
        "PORT": getenv("DATABASE_PORT", 5432),
    }
}

As you can see, i made a test rls_policy as it says in the Quickstart, but it still displays for every user in the admin, regardless of the policy.

Thanks for notifying, checking and verifying if there is any fixes for the same.

Hi @jrol1,

Thanks for reporting this issue! We’ve identified and fixed the root cause.

The Problem:
PostgreSQL RLS policies don’t apply to the table owner by default. Since Django typically connects as the database user who created the tables (the owner), RLS was being silently bypassed - regardless of whether the request came from admin or anywhere else.

The Fix:
We now execute FORCE ROW LEVEL SECURITY after enabling RLS, which ensures policies are enforced even for the table owner.

How to get the fix:

pip install --upgrade django-rls==0.4.1

After upgrading, re-run your migrations or manually call enable_rls() on your models to apply the forced RLS setting:

from your_app.models import ConferencePresentation
ConferencePresentation.enable_rls()

Or run the management command:

python manage.py enable_rls --app your_app

Your policy should now work as expected - staff users in admin will only see ConferencePresentation objects where format_event=“Очный”.

One thing to note: With RLS now properly enforced, the WITH CHECK clause will also apply to inserts/updates. This means you’ll only be able to create or modify records that match your policy filter. If you need different rules for SELECT vs INSERT/UPDATE, you can use separate policies with different operation parameters.

Let us know if you run into any issues after upgrading!