django-superset-integration 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. django_superset_integration-0.1.3/MANIFEST.in +2 -0
  2. django_superset_integration-0.1.3/PKG-INFO +94 -0
  3. django_superset_integration-0.1.3/README.md +81 -0
  4. django_superset_integration-0.1.3/django_superset_integration/__init__.py +0 -0
  5. django_superset_integration-0.1.3/django_superset_integration/admin.py +87 -0
  6. django_superset_integration-0.1.3/django_superset_integration/apps.py +7 -0
  7. django_superset_integration-0.1.3/django_superset_integration/fetch_superset_guest_token.py +129 -0
  8. django_superset_integration-0.1.3/django_superset_integration/forms.py +38 -0
  9. django_superset_integration-0.1.3/django_superset_integration/migrations/0001_initial.py +103 -0
  10. django_superset_integration-0.1.3/django_superset_integration/migrations/__init__.py +0 -0
  11. django_superset_integration-0.1.3/django_superset_integration/models.py +142 -0
  12. django_superset_integration-0.1.3/django_superset_integration/static/django_superset_integration/css/django-superset-integration.css +16 -0
  13. django_superset_integration-0.1.3/django_superset_integration/templates/django_superset_integration/superset-integration.html +72 -0
  14. django_superset_integration-0.1.3/django_superset_integration/urls.py +11 -0
  15. django_superset_integration-0.1.3/django_superset_integration.egg-info/PKG-INFO +94 -0
  16. django_superset_integration-0.1.3/django_superset_integration.egg-info/SOURCES.txt +18 -0
  17. django_superset_integration-0.1.3/django_superset_integration.egg-info/dependency_links.txt +1 -0
  18. django_superset_integration-0.1.3/django_superset_integration.egg-info/top_level.txt +1 -0
  19. django_superset_integration-0.1.3/pyproject.toml +24 -0
  20. django_superset_integration-0.1.3/setup.cfg +4 -0
@@ -0,0 +1,2 @@
1
+ recursive-include django_superset_integration/static *
2
+ recursive-include django_superset_integration/templates *
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-superset-integration
3
+ Version: 0.1.3
4
+ Summary: A Django app to integrate Apache Superset dashboards into a Django application
5
+ Author-email: Alica Burlot <alica.burlot@developpement-durable.gouv.fr>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/B-Alica/django-superset-integration
8
+ Project-URL: Issues, https://github.com/B-Alica/django-superset-integration/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+
14
+ # django-superset-integration
15
+
16
+ `django-superset-integration` is a Django app to integration Apache Superset dashboards into a Django application.
17
+
18
+ ## Quick start
19
+
20
+ 1. Add `django_superset_integration` to your `INSTALLED_APPS` setting like this:
21
+
22
+ ```python
23
+ INSTALLED_APPS = [
24
+ ...,
25
+ "django_superset_integration",
26
+ ...,
27
+ ]
28
+ ```
29
+
30
+ 2. Include the superset-integration URLconf in your project `urls.py` like this:
31
+
32
+ ```python
33
+ path("superset_integration/", include("django_superset_integration.urls")),
34
+ ```
35
+
36
+ 3. You will need a cryptography Fernet key, so you need to install cryptography:
37
+
38
+ ```python
39
+ pip install cryptography
40
+ ```
41
+
42
+ 4. Generate a Fernet key in a python terminal:
43
+
44
+ ```python
45
+ from cryptography.fernet import Fernet
46
+ FERNET_KEY = Fernet.generate_key()
47
+ ```
48
+
49
+ 5. The result is a bytestring like `b'jozEHFGLKJHEFUIHEZ4'`. **Copy ONLY the content of the string, not the b nor the quotation marks**
50
+
51
+ 6. In your env variables, create a variable `FERNET_KEY` with the copied content as value
52
+
53
+ 7. By default, all dashboard data will be displayed. You can override this by creating your own filtering function and adding it in your `settings.py`:
54
+
55
+ ```python
56
+ RLS_FUNCTION = "my_app.my_module.create_rls_clause"
57
+ ```
58
+
59
+ Your function must take a parameter `user` and return a SQL rls clause like this : `[{"clause": "1=1"}]`
60
+ (where you replace 1=1 by the clause you want).
61
+
62
+ See Superset documentation for more information
63
+
64
+ 8. Make sure that your Superset instance parameter `GUEST_TOKEN_JWT_EXP_SECONDS` is more than 300 (5 minutes). Otherwise it will expire before it can be refreshed. For example, set it to 600 (10 minutes).
65
+
66
+ 9. In the template where you want to integrate the dashboard, add the following in your `<head>`:
67
+
68
+ ```html
69
+ <link href="{% static 'css/ponctual-rejects.css' %}" rel="stylesheet"/>
70
+ ```
71
+
72
+ 10. Then add the following at the emplacement where you want the dashboard:
73
+
74
+ ```html
75
+ {% include "django_superset_integration/superset-integration.html" %}
76
+ ```
77
+
78
+ 11. In your view's `get_context_data`, add the following:
79
+
80
+ ```python
81
+ dashboard_name = my_dashboard
82
+ dashboard = SupersetDashboard.objects.get(name__iexact=dashboard_name)
83
+ context["dashboard_integration_id"] = dashboard.integration_id
84
+ context["dashboard_id"] = dashboard.id
85
+ context["superset_domain"] = dashboard.domain.address
86
+ ```
87
+
88
+ 12. Run `python manage.py migrate` to create the models.
89
+
90
+ 13. Start the development server and visit the admin site to create a `SupersetInstance` object.
91
+
92
+ 14. After you have created a `SupersetInstance` object, create a `SupersetDashboard` object.
93
+
94
+ 15. That should be it!
@@ -0,0 +1,81 @@
1
+ # django-superset-integration
2
+
3
+ `django-superset-integration` is a Django app to integration Apache Superset dashboards into a Django application.
4
+
5
+ ## Quick start
6
+
7
+ 1. Add `django_superset_integration` to your `INSTALLED_APPS` setting like this:
8
+
9
+ ```python
10
+ INSTALLED_APPS = [
11
+ ...,
12
+ "django_superset_integration",
13
+ ...,
14
+ ]
15
+ ```
16
+
17
+ 2. Include the superset-integration URLconf in your project `urls.py` like this:
18
+
19
+ ```python
20
+ path("superset_integration/", include("django_superset_integration.urls")),
21
+ ```
22
+
23
+ 3. You will need a cryptography Fernet key, so you need to install cryptography:
24
+
25
+ ```python
26
+ pip install cryptography
27
+ ```
28
+
29
+ 4. Generate a Fernet key in a python terminal:
30
+
31
+ ```python
32
+ from cryptography.fernet import Fernet
33
+ FERNET_KEY = Fernet.generate_key()
34
+ ```
35
+
36
+ 5. The result is a bytestring like `b'jozEHFGLKJHEFUIHEZ4'`. **Copy ONLY the content of the string, not the b nor the quotation marks**
37
+
38
+ 6. In your env variables, create a variable `FERNET_KEY` with the copied content as value
39
+
40
+ 7. By default, all dashboard data will be displayed. You can override this by creating your own filtering function and adding it in your `settings.py`:
41
+
42
+ ```python
43
+ RLS_FUNCTION = "my_app.my_module.create_rls_clause"
44
+ ```
45
+
46
+ Your function must take a parameter `user` and return a SQL rls clause like this : `[{"clause": "1=1"}]`
47
+ (where you replace 1=1 by the clause you want).
48
+
49
+ See Superset documentation for more information
50
+
51
+ 8. Make sure that your Superset instance parameter `GUEST_TOKEN_JWT_EXP_SECONDS` is more than 300 (5 minutes). Otherwise it will expire before it can be refreshed. For example, set it to 600 (10 minutes).
52
+
53
+ 9. In the template where you want to integrate the dashboard, add the following in your `<head>`:
54
+
55
+ ```html
56
+ <link href="{% static 'css/ponctual-rejects.css' %}" rel="stylesheet"/>
57
+ ```
58
+
59
+ 10. Then add the following at the emplacement where you want the dashboard:
60
+
61
+ ```html
62
+ {% include "django_superset_integration/superset-integration.html" %}
63
+ ```
64
+
65
+ 11. In your view's `get_context_data`, add the following:
66
+
67
+ ```python
68
+ dashboard_name = my_dashboard
69
+ dashboard = SupersetDashboard.objects.get(name__iexact=dashboard_name)
70
+ context["dashboard_integration_id"] = dashboard.integration_id
71
+ context["dashboard_id"] = dashboard.id
72
+ context["superset_domain"] = dashboard.domain.address
73
+ ```
74
+
75
+ 12. Run `python manage.py migrate` to create the models.
76
+
77
+ 13. Start the development server and visit the admin site to create a `SupersetInstance` object.
78
+
79
+ 14. After you have created a `SupersetInstance` object, create a `SupersetDashboard` object.
80
+
81
+ 15. That should be it!
@@ -0,0 +1,87 @@
1
+ from django.contrib import admin
2
+ from django.contrib.admin.models import DELETION
3
+ from django.utils.html import escape, format_html
4
+ from django.urls import reverse
5
+
6
+ from .models import (
7
+ SupersetInstance,
8
+ SupersetDashboard,
9
+ )
10
+ from .forms import SupersetInstanceCreationForm
11
+
12
+
13
+ @admin.register(SupersetInstance)
14
+ class SupersetInstanceAdmin(admin.ModelAdmin):
15
+ """
16
+ Admin model for SupersetInstance
17
+ Displayed name: "Instance Superset"
18
+
19
+ :model:`SupersetInstance`
20
+ """
21
+
22
+ add_form = SupersetInstanceCreationForm
23
+ model = SupersetInstance
24
+
25
+ list_display = (
26
+ "address",
27
+ "username",
28
+ )
29
+
30
+ list_filter = ("username",)
31
+
32
+ search_fields = (
33
+ "address",
34
+ "username",
35
+ )
36
+
37
+ ordering = (
38
+ "address",
39
+ "username",
40
+ )
41
+
42
+ def save_form(self, request, form, change):
43
+ """
44
+ Override admin.ModelAdmin.save_form
45
+ """
46
+ if not change:
47
+ form.instance = self.model.objects.create(
48
+ address=form.cleaned_data["address"],
49
+ username=form.cleaned_data["username"],
50
+ )
51
+ form.instance.set_password(form.cleaned_data["password"])
52
+ return super().save_form(request, form, change)
53
+
54
+ def get_fields(self, request, obj=None):
55
+ """
56
+ Exclude field "password" in change form
57
+ """
58
+ fields = super().get_fields(request, obj)
59
+ if obj:
60
+ fields.remove("password")
61
+ return fields
62
+
63
+
64
+ @admin.register(SupersetDashboard)
65
+ class SupersetDashboardAdmin(admin.ModelAdmin):
66
+ """
67
+ Admin model for SupersetDashboard
68
+
69
+ :model:`SupersetDashboard`
70
+ """
71
+
72
+ list_per_page = 20
73
+
74
+ list_display = (
75
+ "name",
76
+ "integration_id",
77
+ "domain",
78
+ )
79
+
80
+ ordering = ("name",)
81
+
82
+ search_fields = (
83
+ "name",
84
+ "domain",
85
+ )
86
+
87
+ list_filter = ("domain",)
@@ -0,0 +1,7 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class SupersetIntegrationConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_superset_integration"
7
+ label = "superset_integration"
@@ -0,0 +1,129 @@
1
+ import importlib
2
+ import requests
3
+ from cryptography.fernet import Fernet
4
+
5
+ from django.http import HttpResponse
6
+ from django.views.decorators.http import require_safe
7
+ from django.conf import settings
8
+
9
+ from .models import SupersetDashboard
10
+
11
+
12
+ def create_rls_clause(user):
13
+ """
14
+ SQL clause to apply to the dashboard data
15
+ Can be adjusted to limit data displayed in the dashboard
16
+ Clause "1=1" apply no filter, so we get all the data
17
+ Clause "1=0" get no data
18
+
19
+ Return format : [{"clause": "1=1"}]
20
+ """
21
+
22
+ if not user:
23
+ # No data
24
+ return [{"clause": "1=0"}]
25
+
26
+ # All data
27
+ return [{"clause": "1=1"}]
28
+
29
+
30
+ @require_safe
31
+ def fetch_superset_guest_token(request, dashboard_id: str):
32
+ """
33
+ Get a guest token for integration of a Superset dashboard
34
+ 1 - Get an access token
35
+ 2 - Get a CSRF token by using the access token
36
+ 3 - Get a guest token by using the CSRF token
37
+
38
+ Return a HttpResponse with the guest token
39
+
40
+ Parameters:
41
+ request
42
+ dashboard_id (str): SupersetDashboard object ID in database
43
+ (not to be confused with attribute integration_id of
44
+ SupersetDashboard object, which is used to integrate the
45
+ dashboard with SupersetEmbeddedSdk)
46
+ """
47
+ # Get a guest_token from Superset API
48
+ # The guest_token allow client access to the Superset dashboard
49
+ with requests.Session() as session:
50
+ dashboard = SupersetDashboard.objects.get(id=int(dashboard_id))
51
+ dashboard_integration_id = dashboard.integration_id
52
+ superset_domain = dashboard.domain.address
53
+ superset_username = dashboard.domain.username
54
+
55
+ # Authentication for API access
56
+ url = f"https://{superset_domain}/api/v1/security/login"
57
+
58
+ def get_password(password):
59
+ cipher_suite = Fernet(settings.ENCRYPTION_KEY)
60
+ decrypted_password = cipher_suite.decrypt(password.encode())
61
+ return decrypted_password.decode()
62
+
63
+ params = {
64
+ "provider": "db",
65
+ "refresh": "True",
66
+ "username": superset_username,
67
+ "password": get_password(dashboard.domain.password),
68
+ }
69
+ session.headers.update({"Content-Type": "application/json"})
70
+ response = session.post(url, json=params)
71
+
72
+ access_token = response.json()["access_token"]
73
+
74
+ session.headers.update({"Authorization": f"Bearer {access_token}"})
75
+ url = f"https://{superset_domain}/api/v1/security/csrf_token/"
76
+ response = session.get(url)
77
+ csrf_token = response.json()["result"]
78
+
79
+ cookie = response.headers["set-cookie"].split("; ")[0]
80
+
81
+ try:
82
+ user = request.user
83
+ except Exception:
84
+ # If no user, no guest_token
85
+ return False
86
+
87
+ # SQL clause to apply to the dashboard data
88
+ # Can be adjusted to limit data displayed in the dashboard
89
+ # Clause "1=1" apply no filter, so we get all the data
90
+ # Clause "1=0" get no data
91
+ # the default function can be overridden by creating
92
+ # your own and setting its path in settings.RLS_FUNCTION
93
+ rls_function_path = getattr(settings, "RLS_FUNCTION", None)
94
+ if rls_function_path:
95
+ # Dynamically import the function
96
+ module_path, func_name = rls_function_path.rsplit(".", 1)
97
+ module = importlib.import_module(module_path)
98
+ rls_function = getattr(module, func_name)
99
+ else:
100
+ rls_function = create_rls_clause
101
+
102
+ rls = rls_function(user)
103
+
104
+ if not rls:
105
+ # If no rls clause, show no data
106
+ rls = [{"clause": "1=0"}]
107
+
108
+ # Get guest_token to display the dashboard
109
+ params = {
110
+ "resources": [
111
+ {"id": dashboard_integration_id, "type": "dashboard"}
112
+ ],
113
+ "rls": rls,
114
+ "user": {
115
+ "first_name": "Prenom",
116
+ "last_name": "Nom",
117
+ "username": superset_username,
118
+ },
119
+ }
120
+ headers = {
121
+ "Content-Type": "application/json",
122
+ "X-Csrftoken": csrf_token,
123
+ "Cookie": cookie,
124
+ }
125
+ session.headers.update(headers)
126
+ url = f"https://{superset_domain}/api/v1/security/guest_token/"
127
+ response = session.post(url, json=params)
128
+ guest_token = response.json()["token"]
129
+ return HttpResponse(guest_token)
@@ -0,0 +1,38 @@
1
+ from django import forms
2
+ from django.utils.translation import gettext_lazy as _
3
+
4
+ from .models import SupersetInstance
5
+
6
+
7
+ class SupersetInstanceCreationForm(forms.ModelForm):
8
+ """
9
+ SupersetInstance creation form
10
+ """
11
+
12
+ address = forms.CharField(
13
+ widget=forms.TextInput(
14
+ attrs={"autofocus": True, "required": True, "id": "address-field"}
15
+ )
16
+ )
17
+
18
+ username = forms.CharField(
19
+ widget=forms.TextInput(
20
+ attrs={"required": True, "id": "username-field"}
21
+ )
22
+ )
23
+
24
+ password = forms.CharField(
25
+ label=_("Password"),
26
+ strip=False,
27
+ widget=forms.PasswordInput(
28
+ attrs={"required": True, id: "password-field"}
29
+ ),
30
+ )
31
+
32
+ class Meta:
33
+ model = SupersetInstance
34
+ fields = (
35
+ "address",
36
+ "username",
37
+ "password",
38
+ )
@@ -0,0 +1,103 @@
1
+ # Generated by Django 5.1.4 on 2025-03-03 12:27
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = []
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="SupersetInstance",
16
+ fields=[
17
+ (
18
+ "id",
19
+ models.BigAutoField(
20
+ auto_created=True,
21
+ primary_key=True,
22
+ serialize=False,
23
+ verbose_name="ID",
24
+ ),
25
+ ),
26
+ (
27
+ "address",
28
+ models.CharField(
29
+ max_length=250,
30
+ unique=True,
31
+ verbose_name="adresse de l'instance Superset",
32
+ ),
33
+ ),
34
+ (
35
+ "username",
36
+ models.CharField(
37
+ default="superset_api",
38
+ max_length=250,
39
+ verbose_name="nom d'utilisateur",
40
+ ),
41
+ ),
42
+ (
43
+ "password",
44
+ models.CharField(
45
+ blank=True,
46
+ max_length=128,
47
+ null=True,
48
+ verbose_name="mot de passe",
49
+ ),
50
+ ),
51
+ ],
52
+ ),
53
+ migrations.CreateModel(
54
+ name="SupersetDashboard",
55
+ fields=[
56
+ (
57
+ "id",
58
+ models.BigAutoField(
59
+ auto_created=True,
60
+ primary_key=True,
61
+ serialize=False,
62
+ verbose_name="ID",
63
+ ),
64
+ ),
65
+ (
66
+ "integration_id",
67
+ models.CharField(
68
+ max_length=50,
69
+ unique=True,
70
+ verbose_name="ID d'intégration",
71
+ ),
72
+ ),
73
+ (
74
+ "name",
75
+ models.CharField(
76
+ max_length=250, unique=True, verbose_name="Nom"
77
+ ),
78
+ ),
79
+ (
80
+ "comment",
81
+ models.TextField(
82
+ blank=True, null=True, verbose_name="Commentaire"
83
+ ),
84
+ ),
85
+ (
86
+ "superset_link",
87
+ models.CharField(
88
+ blank=True,
89
+ max_length=500,
90
+ null=True,
91
+ verbose_name="Lien vers le dashboard dans Superset",
92
+ ),
93
+ ),
94
+ (
95
+ "domain",
96
+ models.ForeignKey(
97
+ on_delete=django.db.models.deletion.CASCADE,
98
+ to="superset_integration.supersetinstance",
99
+ ),
100
+ ),
101
+ ],
102
+ ),
103
+ ]
@@ -0,0 +1,142 @@
1
+ from django.db import models
2
+ from django.conf import settings
3
+ from django.contrib.admin.models import LogEntry
4
+ from django.db.models.signals import pre_save
5
+ from django.dispatch import receiver
6
+
7
+ from cryptography.fernet import Fernet
8
+
9
+
10
+ class SupersetInstance(models.Model):
11
+ """
12
+ SupersetInstance model containing your Superset instance's address
13
+ and the username to access it
14
+ ...
15
+
16
+ Attributes
17
+ ----------
18
+ address: str
19
+ Url address of your Superset instance
20
+
21
+ username: str
22
+ Username to access your Superset instance
23
+ By default: "superset_api"
24
+
25
+ password: str
26
+ Password to access your Superset instance
27
+
28
+ Methods
29
+ ----------
30
+ set_password(self, raw_password):
31
+ Set password in password field after hashing it with django.contrib.auth.hashers.make_password
32
+ """
33
+
34
+ address = models.CharField(
35
+ "adresse de l'instance Superset",
36
+ max_length=250,
37
+ blank=False,
38
+ null=False,
39
+ unique=True,
40
+ )
41
+
42
+ username = models.CharField(
43
+ "nom d'utilisateur",
44
+ max_length=250,
45
+ blank=False,
46
+ null=False,
47
+ default="superset_api",
48
+ )
49
+
50
+ password = models.CharField(
51
+ "mot de passe",
52
+ max_length=128,
53
+ blank=True,
54
+ null=True,
55
+ )
56
+
57
+ def __str__(self):
58
+ return f"Instance Superset : {self.address}"
59
+
60
+ def set_password(self, raw_password):
61
+ cipher_suite = Fernet(settings.ENCRYPTION_KEY)
62
+ encrypted_password = cipher_suite.encrypt(raw_password.encode())
63
+ self.password = encrypted_password.decode()
64
+
65
+
66
+ @receiver(pre_save, sender=SupersetInstance)
67
+ def superset_instance_pre_save_handler(sender, instance, *args, **kwargs):
68
+ """
69
+ SupersetInstance.address should not begin with "http://" or "https://"
70
+ so we remove this part if necessary
71
+ """
72
+
73
+ if instance.address.startswith("http://") or instance.address.startswith(
74
+ "https://"
75
+ ):
76
+ instance.address = instance.address.split("//", 1)[1]
77
+
78
+
79
+ class SupersetDashboard(models.Model):
80
+ """
81
+ SupersetDashboard containing the integration ID and
82
+ name of your Superset dashboard
83
+ ...
84
+
85
+ Attributes
86
+ ----------
87
+ integration_id: str
88
+ Integratrion ID of your Superset dashboard
89
+ Format : xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
90
+ where x are numbers and letters
91
+
92
+ name: str
93
+ Name of the dashboard
94
+
95
+ domain: SupersetInstance
96
+ Foreign key
97
+ Superset instance containing the dashboard
98
+
99
+ comment: str
100
+ Free text field for your comments
101
+
102
+ superset_link: str
103
+ Link to the dashboard in Superset
104
+ """
105
+
106
+ integration_id = models.CharField(
107
+ "ID d'intégration",
108
+ max_length=50,
109
+ blank=False,
110
+ null=False,
111
+ unique=True,
112
+ )
113
+
114
+ name = models.CharField(
115
+ "Nom",
116
+ max_length=250,
117
+ blank=False,
118
+ null=False,
119
+ unique=True,
120
+ )
121
+
122
+ domain = models.ForeignKey(
123
+ SupersetInstance,
124
+ on_delete=models.CASCADE,
125
+ null=False,
126
+ )
127
+
128
+ comment = models.TextField(
129
+ "Commentaire",
130
+ blank=True,
131
+ null=True,
132
+ )
133
+
134
+ superset_link = models.CharField(
135
+ "Lien vers le dashboard dans Superset",
136
+ max_length=500,
137
+ blank=True,
138
+ null=True,
139
+ )
140
+
141
+ def __str__(self):
142
+ return f"Dashboard : {self.name}"
@@ -0,0 +1,16 @@
1
+ #superset-fullscreen-exit {
2
+ visibility: hidden;
3
+ z-index: 1;
4
+ position: fixed;
5
+ top: 10px;
6
+ right: 10px;
7
+ }
8
+
9
+ .fullScreen {
10
+ width: 100%;
11
+ height: 100%;
12
+ z-index: 0;
13
+ position: absolute;
14
+ top: 0;
15
+ left: 0;
16
+ }
@@ -0,0 +1,72 @@
1
+ {% load static %}
2
+
3
+ <link href="{% static 'superset_integration/css/superset-integration.css' %}" rel="stylesheet"/>
4
+
5
+ <button id="superset-fullscreen" class="fr-btn fr-mb-3w">
6
+ Afficher en plein écran
7
+ </button>
8
+
9
+ <button id="superset-fullscreen-exit" class="fr-btn">
10
+ Quitter le mode plein écran
11
+ </button>
12
+
13
+ <div id="superset-integration">
14
+ </div>
15
+
16
+ <script src="{% static 'js/superset-embedded-sdk.js' %}"></script>
17
+ <script>
18
+ // Get guest token from django app backend
19
+ // The token has a lifetime of 5 min. This function is called
20
+ // periodically to refresh the token
21
+ async function fetchGuestTokenFromBackend() {
22
+ const response = await fetch('/superset_integration/guest_token/{{ dashboard_id }}');
23
+ const token = await response.text();
24
+ return token;
25
+ }
26
+
27
+ // Display dashboard inside #graph element
28
+ supersetEmbeddedSdk.embedDashboard({
29
+ id: "{{ dashboard_integration_id }}",
30
+ supersetDomain: "https://" + "{{ superset_domain }}",
31
+ mountPoint: document.getElementById("superset-integration"),
32
+ fetchGuestToken: () => fetchGuestTokenFromBackend(),
33
+ dashboardUiConfig: {
34
+ hideTitle: true,
35
+ filters: {
36
+ expanded: true,
37
+ }
38
+ },
39
+ });
40
+
41
+ let iframe = document.getElementById("superset-integration").firstElementChild;
42
+ let iframe_height = "1200px";
43
+
44
+ $(window).on('load',function () {
45
+ {% if iframe_height and iframe_height.isdigit %}
46
+ iframe_height = "{{ iframe_height }}px";
47
+ {% elif iframe_height %}
48
+ iframe_height = "{{ iframe_height }}";
49
+ {% endif %}
50
+ iframe.height = iframe_height;
51
+ });
52
+
53
+ $("#superset-fullscreen").click(function(ev)
54
+ {
55
+ $(".fr-header").hide();
56
+ $(".fr-footer").hide();
57
+ $("#superset-fullscreen-exit").show();
58
+ $("#superset-fullscreen-exit").css({visibility: "visible"});
59
+ $("#superset-fullscreen-exit").css({opacity: 100});
60
+ $("#superset-integration").addClass("fullScreen");
61
+ iframe.height = "100%";
62
+ });
63
+
64
+ $("#superset-fullscreen-exit").click(function(ev)
65
+ {
66
+ $(".fr-header").show();
67
+ $(".fr-footer").show();
68
+ $("#superset-fullscreen-exit").hide();
69
+ $("#superset-integration").removeClass("fullScreen");
70
+ iframe.height = iframe_height;
71
+ });
72
+ </script>
@@ -0,0 +1,11 @@
1
+ from django.urls import path
2
+
3
+ from .fetch_superset_guest_token import fetch_superset_guest_token
4
+
5
+ urlpatterns = [
6
+ path(
7
+ "guest_token/<slug:dashboard_id>",
8
+ fetch_superset_guest_token,
9
+ name="guest-token",
10
+ ),
11
+ ]
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-superset-integration
3
+ Version: 0.1.3
4
+ Summary: A Django app to integrate Apache Superset dashboards into a Django application
5
+ Author-email: Alica Burlot <alica.burlot@developpement-durable.gouv.fr>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/B-Alica/django-superset-integration
8
+ Project-URL: Issues, https://github.com/B-Alica/django-superset-integration/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+
14
+ # django-superset-integration
15
+
16
+ `django-superset-integration` is a Django app to integration Apache Superset dashboards into a Django application.
17
+
18
+ ## Quick start
19
+
20
+ 1. Add `django_superset_integration` to your `INSTALLED_APPS` setting like this:
21
+
22
+ ```python
23
+ INSTALLED_APPS = [
24
+ ...,
25
+ "django_superset_integration",
26
+ ...,
27
+ ]
28
+ ```
29
+
30
+ 2. Include the superset-integration URLconf in your project `urls.py` like this:
31
+
32
+ ```python
33
+ path("superset_integration/", include("django_superset_integration.urls")),
34
+ ```
35
+
36
+ 3. You will need a cryptography Fernet key, so you need to install cryptography:
37
+
38
+ ```python
39
+ pip install cryptography
40
+ ```
41
+
42
+ 4. Generate a Fernet key in a python terminal:
43
+
44
+ ```python
45
+ from cryptography.fernet import Fernet
46
+ FERNET_KEY = Fernet.generate_key()
47
+ ```
48
+
49
+ 5. The result is a bytestring like `b'jozEHFGLKJHEFUIHEZ4'`. **Copy ONLY the content of the string, not the b nor the quotation marks**
50
+
51
+ 6. In your env variables, create a variable `FERNET_KEY` with the copied content as value
52
+
53
+ 7. By default, all dashboard data will be displayed. You can override this by creating your own filtering function and adding it in your `settings.py`:
54
+
55
+ ```python
56
+ RLS_FUNCTION = "my_app.my_module.create_rls_clause"
57
+ ```
58
+
59
+ Your function must take a parameter `user` and return a SQL rls clause like this : `[{"clause": "1=1"}]`
60
+ (where you replace 1=1 by the clause you want).
61
+
62
+ See Superset documentation for more information
63
+
64
+ 8. Make sure that your Superset instance parameter `GUEST_TOKEN_JWT_EXP_SECONDS` is more than 300 (5 minutes). Otherwise it will expire before it can be refreshed. For example, set it to 600 (10 minutes).
65
+
66
+ 9. In the template where you want to integrate the dashboard, add the following in your `<head>`:
67
+
68
+ ```html
69
+ <link href="{% static 'css/ponctual-rejects.css' %}" rel="stylesheet"/>
70
+ ```
71
+
72
+ 10. Then add the following at the emplacement where you want the dashboard:
73
+
74
+ ```html
75
+ {% include "django_superset_integration/superset-integration.html" %}
76
+ ```
77
+
78
+ 11. In your view's `get_context_data`, add the following:
79
+
80
+ ```python
81
+ dashboard_name = my_dashboard
82
+ dashboard = SupersetDashboard.objects.get(name__iexact=dashboard_name)
83
+ context["dashboard_integration_id"] = dashboard.integration_id
84
+ context["dashboard_id"] = dashboard.id
85
+ context["superset_domain"] = dashboard.domain.address
86
+ ```
87
+
88
+ 12. Run `python manage.py migrate` to create the models.
89
+
90
+ 13. Start the development server and visit the admin site to create a `SupersetInstance` object.
91
+
92
+ 14. After you have created a `SupersetInstance` object, create a `SupersetDashboard` object.
93
+
94
+ 15. That should be it!
@@ -0,0 +1,18 @@
1
+ MANIFEST.in
2
+ README.md
3
+ pyproject.toml
4
+ django_superset_integration/__init__.py
5
+ django_superset_integration/admin.py
6
+ django_superset_integration/apps.py
7
+ django_superset_integration/fetch_superset_guest_token.py
8
+ django_superset_integration/forms.py
9
+ django_superset_integration/models.py
10
+ django_superset_integration/urls.py
11
+ django_superset_integration.egg-info/PKG-INFO
12
+ django_superset_integration.egg-info/SOURCES.txt
13
+ django_superset_integration.egg-info/dependency_links.txt
14
+ django_superset_integration.egg-info/top_level.txt
15
+ django_superset_integration/migrations/0001_initial.py
16
+ django_superset_integration/migrations/__init__.py
17
+ django_superset_integration/static/django_superset_integration/css/django-superset-integration.css
18
+ django_superset_integration/templates/django_superset_integration/superset-integration.html
@@ -0,0 +1 @@
1
+ django_superset_integration
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "django-superset-integration"
7
+ version = "0.1.3"
8
+ description = "A Django app to integrate Apache Superset dashboards into a Django application"
9
+ authors = [
10
+ {name = "Alica Burlot",email = "alica.burlot@developpement-durable.gouv.fr"}
11
+ ]
12
+ license = "GPL-3.0-or-later"
13
+ readme = "README.md"
14
+ requires-python = ">=3.10"
15
+ dependencies = [
16
+ ]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/B-Alica/django-superset-integration"
24
+ Issues = "https://github.com/B-Alica/django-superset-integration/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+