Skip to content

Commit 9136b61

Browse files
feat(ui): enhance code blocks with syntax highlighting and copy buttons
1 parent 9ef6c42 commit 9136b61

File tree

7 files changed

+223
-131
lines changed

7 files changed

+223
-131
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ cover/
6060
local_settings.py
6161
db.sqlite3
6262
db.sqlite3-journal
63+
staticfiles/
6364

6465
# Flask stuff:
6566
instance/

example_project/sample_app/anonymization_views.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,10 @@ def context_manager_demo(request: HttpRequest) -> HttpResponse:
3737
}
3838

3939
try:
40-
# Normal data access
41-
normal_users = list(User.objects.values("username", "email", "first_name", "last_name")[:5])
42-
context["normal_data"] = normal_users
40+
# Normal data access - explicitly use default role to bypass middleware
41+
with database_role("sanyamkhurana"): # Use default role to get unmasked data
42+
normal_users = list(User.objects.values("username", "email", "first_name", "last_name")[:5])
43+
context["normal_data"] = normal_users
4344

4445
# Using anonymized_data context manager
4546
try:

example_project/sample_app/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from datetime import datetime, timedelta
88

99
from django.contrib import messages
10-
from django.contrib.auth.decorators import user_passes_test
10+
from django.contrib.auth.decorators import login_required, user_passes_test
1111
from django.contrib.auth.models import User
1212
from django.core.paginator import Paginator
1313
from django.db import transaction
@@ -182,7 +182,7 @@ def create_demo_data(request: HttpRequest) -> HttpResponse:
182182
return render(request, "sample_app/create_demo_data.html")
183183

184184

185-
@user_passes_test(lambda u: u.is_superuser)
185+
@login_required
186186
def anonymization_demo(request: HttpRequest) -> HttpResponse:
187187
"""Demonstrate anonymization features"""
188188

example_project/templates/sample_app/anonymized_customer_view.html

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
{% block title %}Anonymized Customer View - Django PostgreSQL Anonymizer{% endblock %}
44

5+
{% block extra_css %}
6+
{% endblock %}
7+
58
{% block content %}
69
<div class="row">
710
<div class="col-12">
@@ -219,23 +222,28 @@ <h5 class="mb-0">
219222
</div>
220223
<div class="card-body">
221224
<h6>Class-based View with Mixin</h6>
222-
<pre class="bg-light p-3 rounded"><code>from django_postgres_anon.decorators import AnonymizedDataMixin
223-
from django.views.generic import TemplateView
225+
<div style="position: relative;">
226+
<pre style="background: #f6f8fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 0; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 13px; line-height: 1.45;"><code><span style="color: #d73a49; font-weight: 600;">from</span> django_postgres_anon.decorators <span style="color: #d73a49; font-weight: 600;">import</span> AnonymizedDataMixin
227+
<span style="color: #d73a49; font-weight: 600;">from</span> django.views.generic <span style="color: #d73a49; font-weight: 600;">import</span> TemplateView
224228

225-
class AnonymizedCustomerView(AnonymizedDataMixin, TemplateView):
226-
template_name = 'sample_app/anonymized_customer_view.html'
229+
<span style="color: #d73a49; font-weight: 600;">class</span> <span style="color: #6f42c1;">AnonymizedCustomerView</span>(<span style="color: #6f42c1;">AnonymizedDataMixin</span>, <span style="color: #6f42c1;">TemplateView</span>):
230+
template_name = <span style="color: #032f62;">'sample_app/anonymized_customer_view.html'</span>
227231

228-
def get_context_data(self, **kwargs):
229-
context = super().get_context_data(**kwargs)
232+
<span style="color: #d73a49; font-weight: 600;">def</span> <span style="color: #6f42c1;">get_context_data</span>(<span style="color: #e36209;">self</span>, **kwargs):
233+
context = <span style="color: #005cc5;">super</span>().<span style="color: #6f42c1;">get_context_data</span>(**kwargs)
230234

231-
# This query will automatically use anonymized data
232-
customers = Customer.objects.select_related('user').all()[:10]
235+
<span style="color: #6a737d; font-style: italic;"># This query will automatically use anonymized data</span>
236+
customers = Customer.objects.<span style="color: #6f42c1;">select_related</span>(<span style="color: #032f62;">'user'</span>).<span style="color: #6f42c1;">all</span>()[<span style="color: #005cc5;">:</span><span style="color: #005cc5;">10</span>]
233237

234-
context.update({
235-
'customers': customers,
236-
'mixin_used': 'AnonymizedDataMixin',
238+
context.<span style="color: #6f42c1;">update</span>({
239+
<span style="color: #032f62;">'customers'</span>: customers,
240+
<span style="color: #032f62;">'mixin_used'</span>: <span style="color: #032f62;">'AnonymizedDataMixin'</span>,
237241
})
238-
return context</code></pre>
242+
<span style="color: #d73a49; font-weight: 600;">return</span> context</code></pre>
243+
<button onclick="copyCode(this)" style="position: absolute; top: 8px; right: 8px; background: #0366d6; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; opacity: 0.7;" title="Copy code">
244+
Copy
245+
</button>
246+
</div>
239247

240248
<div class="mt-3">
241249
<h6>Key Features</h6>
@@ -274,3 +282,43 @@ <h6>Key Features</h6>
274282
</div>
275283
</div>
276284
{% endblock %}
285+
286+
{% block extra_js %}
287+
<script>
288+
function copyCode(button) {
289+
const codeBlock = button.previousElementSibling.querySelector('code');
290+
const text = codeBlock.textContent;
291+
292+
navigator.clipboard.writeText(text).then(function() {
293+
const originalText = button.textContent;
294+
button.textContent = 'Copied!';
295+
button.style.background = '#28a745';
296+
button.style.opacity = '1';
297+
298+
setTimeout(function() {
299+
button.textContent = originalText;
300+
button.style.background = '#0366d6';
301+
button.style.opacity = '0.7';
302+
}, 2000);
303+
}).catch(function(err) {
304+
const textArea = document.createElement('textarea');
305+
textArea.value = text;
306+
document.body.appendChild(textArea);
307+
textArea.select();
308+
document.execCommand('copy');
309+
document.body.removeChild(textArea);
310+
311+
const originalText = button.textContent;
312+
button.textContent = 'Copied!';
313+
button.style.background = '#28a745';
314+
button.style.opacity = '1';
315+
316+
setTimeout(function() {
317+
button.textContent = originalText;
318+
button.style.background = '#0366d6';
319+
button.style.opacity = '0.7';
320+
}, 2000);
321+
});
322+
}
323+
</script>
324+
{% endblock %}

example_project/templates/sample_app/context_manager_demo.html

Lines changed: 85 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
{% block content %}
66
<div class="row">
77
<div class="col-12">
8-
<div class="card mb-4">
9-
<div class="card-header">
8+
<div class="card mb-4" style="border: 3px solid #28a745; background: linear-gradient(45deg, #f8f9fa, #e9ecef);">
9+
<div class="card-header" style="background: linear-gradient(45deg, #28a745, #20c997); color: white;">
1010
<h4 class="mb-0">
11-
<i class="fas fa-code me-2"></i>{{ demo_title }}
11+
<i class="fas fa-code me-2"></i>Context Manager Demo
1212
</h4>
1313
</div>
1414
<div class="card-body">
@@ -31,7 +31,12 @@ <h5 class="mb-0">
3131
</div>
3232
<div class="card-body">
3333
<p class="small text-muted">Direct database queries without anonymization:</p>
34-
<pre class="bg-light p-2 rounded"><code>User.objects.values('username', 'email')[:5]</code></pre>
34+
<div class="code-block" style="position: relative;">
35+
<pre style="background: #f6f8fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 0; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 13px; line-height: 1.45;"><code>User.objects.<span style="color: #6f42c1; font-weight: 600;">values</span>(<span style="color: #032f62;">'username'</span>, <span style="color: #032f62;">'email'</span>)[<span style="color: #005cc5;">:</span><span style="color: #005cc5;">5</span>]</code></pre>
36+
<button onclick="copyCode(this)" style="position: absolute; top: 8px; right: 8px; background: #0366d6; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; opacity: 0.7;" title="Copy code">
37+
Copy
38+
</button>
39+
</div>
3540

3641
<div class="mt-3">
3742
{% for user in normal_data %}
@@ -58,10 +63,15 @@ <h5 class="mb-0">
5863
</div>
5964
<div class="card-body">
6065
<p class="small text-muted">Using <code>anonymized_data()</code> context manager:</p>
61-
<pre class="bg-light p-2 rounded"><code>from django_postgres_anon.context_managers import anonymized_data
62-
63-
with anonymized_data():
64-
User.objects.values('username', 'email')[:5]</code></pre>
66+
<div style="position: relative;">
67+
<pre style="background: #f6f8fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 0; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 13px; line-height: 1.45;"><code><span style="color: #d73a49; font-weight: 600;">from</span> django_postgres_anon.context_managers <span style="color: #d73a49; font-weight: 600;">import</span> anonymized_data
68+
69+
<span style="color: #d73a49; font-weight: 600;">with</span> <span style="color: #6f42c1;">anonymized_data</span>():
70+
User.objects.<span style="color: #6f42c1;">values</span>(<span style="color: #032f62;">'username'</span>, <span style="color: #032f62;">'email'</span>)[<span style="color: #005cc5;">:</span><span style="color: #005cc5;">5</span>]</code></pre>
71+
<button onclick="copyCode(this)" style="position: absolute; top: 8px; right: 8px; background: #0366d6; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; opacity: 0.7;" title="Copy code">
72+
Copy
73+
</button>
74+
</div>
6575

6676
<div class="mt-3">
6777
{% for user in anonymized_data %}
@@ -94,10 +104,15 @@ <h5 class="mb-0">
94104
</div>
95105
<div class="card-body">
96106
<p class="small text-muted">Using <code>database_role()</code> context manager:</p>
97-
<pre class="bg-light p-2 rounded"><code>from django_postgres_anon.context_managers import database_role
98-
99-
with database_role('masked_reader'):
100-
User.objects.values('username', 'email')[:5]</code></pre>
107+
<div style="position: relative;">
108+
<pre style="background: #f6f8fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 0; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 13px; line-height: 1.45;"><code><span style="color: #d73a49; font-weight: 600;">from</span> django_postgres_anon.context_managers <span style="color: #d73a49; font-weight: 600;">import</span> database_role
109+
110+
<span style="color: #d73a49; font-weight: 600;">with</span> <span style="color: #6f42c1;">database_role</span>(<span style="color: #032f62;">'masked_reader'</span>):
111+
User.objects.<span style="color: #6f42c1;">values</span>(<span style="color: #032f62;">'username'</span>, <span style="color: #032f62;">'email'</span>)[<span style="color: #005cc5;">:</span><span style="color: #005cc5;">5</span>]</code></pre>
112+
<button onclick="copyCode(this)" style="position: absolute; top: 8px; right: 8px; background: #0366d6; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; opacity: 0.7;" title="Copy code">
113+
Copy
114+
</button>
115+
</div>
101116

102117
<div class="mt-3">
103118
{% for user in role_switched_data %}
@@ -125,97 +140,72 @@ <h5 class="mb-0">
125140
<div class="row">
126141
<div class="col-12">
127142
<div class="card mb-4">
128-
<div class="card-header bg-info text-white">
143+
<div class="card-header" style="background: linear-gradient(45deg, #17a2b8, #138496); color: white;">
129144
<h5 class="mb-0">
130-
<i class="fas fa-copy me-2"></i>Quick Start - Copy & Paste Ready
145+
<i class="fas fa-copy me-2"></i>Quick Start - Copy & Paste Ready
131146
</h5>
132147
</div>
133148
<div class="card-body">
134149
<p class="mb-3">Ready to use this in your project? Copy and paste this code:</p>
135-
<pre class="bg-light p-3 rounded"><code># Import the context managers
136-
from django_postgres_anon.context_managers import anonymized_data, database_role
137-
138-
# Basic usage in your views
139-
def my_view(request):
140-
# Get anonymized data
141-
with anonymized_data():
142-
users = User.objects.all()
143-
144-
# Or use specific role
145-
with database_role('masked_reader'):
146-
customers = Customer.objects.all()
147-
148-
return render(request, 'template.html', {'users': users})</code></pre>
149-
</div>
150-
</div>
151-
</div>
152-
</div>
153-
154-
<!-- Code Examples -->
155-
<div class="row">
156-
<div class="col-12">
157-
<div class="card">
158-
<div class="card-header">
159-
<h5 class="mb-0">
160-
<i class="fas fa-code me-2"></i>Implementation Examples
161-
</h5>
162-
</div>
163-
<div class="card-body">
164-
<div class="row">
165-
<div class="col-md-6">
166-
<h6>Context Manager Usage</h6>
167-
<pre class="bg-light p-3 rounded"><code># Temporary anonymized access
168-
from django_postgres_anon.context_managers import anonymized_data
169-
170-
def my_view(request):
171-
# Normal data access
172-
normal_users = User.objects.all()
173-
174-
# Anonymized data access
175-
with anonymized_data():
176-
anon_users = User.objects.all()
177-
178-
return render(request, 'template.html', {
179-
'normal': normal_users,
180-
'anonymized': anon_users
181-
})</code></pre>
182-
</div>
183-
<div class="col-md-6">
184-
<h6>Role Switching</h6>
185-
<pre class="bg-light p-3 rounded"><code># Switch to specific database role
186-
from django_postgres_anon.context_managers import database_role
187-
188-
def admin_view(request):
189-
# Switch to masked reader role
190-
with database_role('masked_reader'):
191-
sensitive_data = Customer.objects.all()
192-
193-
# Back to normal role
194-
normal_data = Customer.objects.all()
195-
196-
return render(request, 'admin.html', {
197-
'masked': sensitive_data,
198-
'normal': normal_data
199-
})</code></pre>
200-
</div>
150+
<div style="position: relative;">
151+
<pre style="background: #f6f8fa; border: 1px solid #e1e4e8; border-radius: 6px; padding: 16px; margin: 0; font-family: 'SFMono-Regular', Consolas, monospace; font-size: 13px; line-height: 1.45;"><code><span style="color: #6a737d; font-style: italic;"># Import the context managers</span>
152+
<span style="color: #d73a49; font-weight: 600;">from</span> django_postgres_anon.context_managers <span style="color: #d73a49; font-weight: 600;">import</span> anonymized_data, database_role
153+
154+
<span style="color: #6a737d; font-style: italic;"># Basic usage in your views</span>
155+
<span style="color: #d73a49; font-weight: 600;">def</span> <span style="color: #6f42c1;">my_view</span>(request):
156+
<span style="color: #6a737d; font-style: italic;"># Get anonymized data</span>
157+
<span style="color: #d73a49; font-weight: 600;">with</span> <span style="color: #6f42c1;">anonymized_data</span>():
158+
users = User.objects.<span style="color: #6f42c1;">all</span>()
159+
160+
<span style="color: #6a737d; font-style: italic;"># Or use specific role</span>
161+
<span style="color: #d73a49; font-weight: 600;">with</span> <span style="color: #6f42c1;">database_role</span>(<span style="color: #032f62;">'masked_reader'</span>):
162+
customers = Customer.objects.<span style="color: #6f42c1;">all</span>()
163+
164+
<span style="color: #d73a49; font-weight: 600;">return</span> <span style="color: #6f42c1;">render</span>(request, <span style="color: #032f62;">'template.html'</span>, {<span style="color: #032f62;">'users'</span>: users})</code></pre>
165+
<button onclick="copyCode(this)" style="position: absolute; top: 8px; right: 8px; background: #0366d6; color: white; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; opacity: 0.7;" title="Copy code">
166+
Copy
167+
</button>
201168
</div>
202169
</div>
203170
</div>
204171
</div>
205172
</div>
206173

207-
<!-- Navigation -->
208-
<div class="row mt-4">
209-
<div class="col-12 text-center">
210-
<a href="{% url 'sample_app:anonymization_demo' %}" class="btn btn-outline-primary me-2">
211-
<i class="fas fa-arrow-left me-1"></i>Back to Demo
212-
</a>
213-
<a href="/api/decorated-users/" class="btn btn-primary me-2" target="_blank">
214-
<i class="fas fa-external-link-alt me-1"></i>Try Decorator API
215-
</a>
216-
<a href="{% url 'sample_app:function_validator_demo' %}" class="btn btn-success">
217-
<i class="fas fa-check-circle me-1"></i>Function Validator
218-
</a>
219-
</div>
220-
</div>
221-
{% endblock %}
174+
<script>
175+
function copyCode(button) {
176+
const codeBlock = button.previousElementSibling.querySelector('code');
177+
const text = codeBlock.textContent;
178+
179+
navigator.clipboard.writeText(text).then(function() {
180+
const originalText = button.textContent;
181+
button.textContent = 'Copied!';
182+
button.style.background = '#28a745';
183+
button.style.opacity = '1';
184+
185+
setTimeout(function() {
186+
button.textContent = originalText;
187+
button.style.background = '#0366d6';
188+
button.style.opacity = '0.7';
189+
}, 2000);
190+
}).catch(function(err) {
191+
const textArea = document.createElement('textarea');
192+
textArea.value = text;
193+
document.body.appendChild(textArea);
194+
textArea.select();
195+
document.execCommand('copy');
196+
document.body.removeChild(textArea);
197+
198+
const originalText = button.textContent;
199+
button.textContent = 'Copied!';
200+
button.style.background = '#28a745';
201+
button.style.opacity = '1';
202+
203+
setTimeout(function() {
204+
button.textContent = originalText;
205+
button.style.background = '#0366d6';
206+
button.style.opacity = '0.7';
207+
}, 2000);
208+
});
209+
}
210+
</script>
211+
{% endblock %}

0 commit comments

Comments
 (0)