Skip to content

Commit d99b407

Browse files
chore: Fix linting with README update
1 parent 9136b61 commit d99b407

File tree

8 files changed

+66
-47
lines changed

8 files changed

+66
-47
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,39 @@ pip install -e .
565565
pip install -r requirements.txt
566566
```
567567

568+
### 🎮 Demo Application
569+
570+
The package includes a fully-featured demo application showcasing all anonymization features:
571+
572+
```bash
573+
# Set up the demo application
574+
cd example_project
575+
pip install -r requirements.txt
576+
577+
# Run migrations
578+
python manage.py migrate
579+
580+
# Create demo data
581+
python manage.py create_demo_data
582+
583+
# Start the development server
584+
python manage.py runserver
585+
586+
# Access the demo at http://localhost:8000
587+
```
588+
589+
#### Demo Features:
590+
- 📊 **Interactive Dashboard** - Visual demonstrations of all anonymization methods
591+
- 🔄 **Context Manager Examples** - Live examples with syntax-highlighted code
592+
- 📝 **Copy-Ready Code Snippets** - Click-to-copy code blocks for quick integration
593+
- 🎭 **Real vs Anonymized Comparison** - Side-by-side data comparison views
594+
- 🛡️ **Role-Based Access Demo** - Automatic anonymization based on user groups
595+
- 🧪 **Function Validator** - Test anonymization functions with live feedback
596+
597+
#### Demo Users:
598+
- **admin/admin** - Superuser with full access to all data
599+
- **testuser/testpassword** - Regular user in `view_masked_data` group (sees anonymized data)
600+
568601
### 🧪 Running Tests
569602

570603
```bash

django_postgres_anon/management/commands/anon_fix_permissions.py

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"""
44

55
from django.core.management.base import BaseCommand
6-
from django.db import connection
76

87
from django_postgres_anon.models import MaskedRole
98
from django_postgres_anon.utils import create_masked_role
@@ -29,20 +28,14 @@ def handle(self, *args, **options):
2928
# Fix specific role
3029
role_name = options["role"]
3130
if self.fix_role_permissions(role_name):
32-
self.stdout.write(
33-
self.style.SUCCESS(f"Successfully fixed permissions for role: {role_name}")
34-
)
31+
self.stdout.write(self.style.SUCCESS(f"Successfully fixed permissions for role: {role_name}"))
3532
else:
36-
self.stdout.write(
37-
self.style.ERROR(f"Failed to fix permissions for role: {role_name}")
38-
)
33+
self.stdout.write(self.style.ERROR(f"Failed to fix permissions for role: {role_name}"))
3934
elif options["all"]:
4035
# Fix all known roles
4136
roles = MaskedRole.objects.all()
4237
if not roles:
43-
self.stdout.write(
44-
self.style.WARNING("No roles found in database")
45-
)
38+
self.stdout.write(self.style.WARNING("No roles found in database"))
4639
return
4740

4841
success_count = 0
@@ -51,25 +44,17 @@ def handle(self, *args, **options):
5144
success_count += 1
5245
self.stdout.write(f"Fixed permissions for: {role.role_name}")
5346
else:
54-
self.stdout.write(
55-
self.style.ERROR(f"Failed to fix permissions for: {role.role_name}")
56-
)
47+
self.stdout.write(self.style.ERROR(f"Failed to fix permissions for: {role.role_name}"))
5748

58-
self.stdout.write(
59-
self.style.SUCCESS(f"Fixed permissions for {success_count}/{len(roles)} roles")
60-
)
49+
self.stdout.write(self.style.SUCCESS(f"Fixed permissions for {success_count}/{len(roles)} roles"))
6150
else:
62-
self.stdout.write(
63-
self.style.ERROR("Please specify --role <name> or --all")
64-
)
51+
self.stdout.write(self.style.ERROR("Please specify --role <name> or --all"))
6552

6653
def fix_role_permissions(self, role_name):
6754
"""Fix permissions for a specific role"""
6855
try:
6956
# Use the create_masked_role function which now includes permission fixing
7057
return create_masked_role(role_name)
7158
except Exception as e:
72-
self.stdout.write(
73-
self.style.ERROR(f"Error fixing permissions for {role_name}: {e}")
74-
)
75-
return False
59+
self.stdout.write(self.style.ERROR(f"Error fixing permissions for {role_name}: {e}"))
60+
return False

django_postgres_anon/utils.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,20 +117,17 @@ def create_masked_role(role_name, inherit_from=None):
117117
# Always ensure the role has proper permissions to django_postgres_anon tables
118118
# This is needed for the role to access the app's models even when switching contexts
119119
django_postgres_anon_tables = [
120-
'django_postgres_anon_maskingrule',
121-
'django_postgres_anon_maskingpreset',
122-
'django_postgres_anon_maskingpreset_rules',
123-
'django_postgres_anon_maskedrole',
124-
'django_postgres_anon_maskinglog'
120+
"django_postgres_anon_maskingrule",
121+
"django_postgres_anon_maskingpreset",
122+
"django_postgres_anon_maskingpreset_rules",
123+
"django_postgres_anon_maskedrole",
124+
"django_postgres_anon_maskinglog",
125125
]
126126

127127
for table in django_postgres_anon_tables:
128128
try:
129129
# Check if table exists first
130-
cursor.execute(
131-
"SELECT 1 FROM information_schema.tables WHERE table_name = %s",
132-
[table]
133-
)
130+
cursor.execute("SELECT 1 FROM information_schema.tables WHERE table_name = %s", [table])
134131
if cursor.fetchone():
135132
# Grant SELECT permissions on django_postgres_anon tables
136133
cursor.execute(
@@ -139,7 +136,7 @@ def create_masked_role(role_name, inherit_from=None):
139136

140137
# For the MaskedRole table, also grant INSERT and UPDATE permissions
141138
# so roles can update their own status
142-
if table == 'django_postgres_anon_maskedrole':
139+
if table == "django_postgres_anon_maskedrole":
143140
try:
144141
cursor.execute(
145142
f"GRANT INSERT, UPDATE ON TABLE {connection.ops.quote_name(table)} TO {connection.ops.quote_name(role_name)}"
@@ -148,7 +145,9 @@ def create_masked_role(role_name, inherit_from=None):
148145
cursor.execute(
149146
f"GRANT USAGE ON SEQUENCE {connection.ops.quote_name(table + '_id_seq')} TO {connection.ops.quote_name(role_name)}"
150147
)
151-
logger.debug(f"Granted INSERT, UPDATE, and USAGE on sequence for {table} to {role_name}")
148+
logger.debug(
149+
f"Granted INSERT, UPDATE, and USAGE on sequence for {table} to {role_name}"
150+
)
152151
except Exception as write_error:
153152
logger.warning(f"Failed to grant write permissions on {table}: {write_error}")
154153

@@ -160,7 +159,9 @@ def create_masked_role(role_name, inherit_from=None):
160159

161160
# Grant CONNECT permission on database
162161
try:
163-
cursor.execute(f"GRANT CONNECT ON DATABASE {connection.ops.quote_name(connection.settings_dict['NAME'])} TO {connection.ops.quote_name(role_name)}")
162+
cursor.execute(
163+
f"GRANT CONNECT ON DATABASE {connection.ops.quote_name(connection.settings_dict['NAME'])} TO {connection.ops.quote_name(role_name)}"
164+
)
164165
logger.debug(f"Granted CONNECT on database to {role_name}")
165166
except Exception as db_error:
166167
logger.warning(f"Failed to grant CONNECT permission: {db_error}")
@@ -373,7 +374,7 @@ def switch_to_role(role_name: str, auto_create: bool = True):
373374
cursor.execute(f"SET ROLE {role_name}")
374375

375376
# For masked roles, also set the search path to prioritize mask schema
376-
if 'mask' in role_name.lower():
377+
if "mask" in role_name.lower():
377378
cursor.execute("SET search_path = mask, public")
378379
logger.debug(f"Set search_path to 'mask, public' for role {role_name}")
379380

example_project/example_project/urls.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from django.contrib import admin
66
from django.urls import include, path
7-
87
from sample_app.masking_demo_views import test_user_data
98
from sample_app.views import index
109

example_project/sample_app/masking_demo_views.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ def test_user_data(request):
88
"""Test view to see if dynamic masking works"""
99
users = User.objects.all()[:5]
1010

11-
user_data = []
12-
for user in users:
13-
user_data.append(
14-
{
15-
"username": user.username,
16-
"email": user.email,
17-
}
18-
)
11+
user_data = [
12+
{
13+
"username": user.username,
14+
"email": user.email,
15+
}
16+
for user in users
17+
]
1918

2019
return JsonResponse(
2120
{

example_project/sample_app/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
app_name = "sample_app"
1111

12+
1213
def redirect_to_home(request):
1314
"""Redirect /sample/ to / to avoid duplicate pages"""
1415
return redirect("/", permanent=True)
1516

17+
1618
urlpatterns = [
1719
path("", redirect_to_home, name="index"),
1820
path("customers/", views.customer_list, name="customer_list"),

example_project/sample_app/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ def anonymization_demo(request: HttpRequest) -> HttpResponse:
273273
from django_postgres_anon import get_preset_path
274274

275275
preset_path = get_preset_path(preset_name)
276-
preset, rules_created = MaskingPreset.load_from_yaml(preset_path, f"{preset_name}_demo")
276+
_preset, rules_created = MaskingPreset.load_from_yaml(preset_path, f"{preset_name}_demo")
277277
messages.success(request, f'Loaded preset "{preset_name}" with {rules_created} rules')
278278
except Exception as e:
279279
messages.error(request, f"Error loading preset: {e!s}")

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ ignore = [
310310
[tool.ruff.lint.per-file-ignores]
311311
"tests/*" = ["E501", "F403", "F405", "S", "PLR", "ERA"]
312312
"*/migrations/*" = ["E501", "ERA"]
313-
"example_project/*" = ["E501", "F403", "F405", "ERA"]
313+
"example_project/*" = ["E501", "F403", "F405", "ERA", "S311"]
314314
"__init__.py" = ["F401"]
315315
"conftest.py" = ["F401", "F403"]
316316

0 commit comments

Comments
 (0)