oxutils 0.1.2__py3-none-any.whl → 0.1.6__py3-none-any.whl

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 (55) hide show
  1. oxutils/__init__.py +1 -1
  2. oxutils/audit/settings.py +1 -16
  3. oxutils/audit/utils.py +22 -0
  4. oxutils/conf.py +1 -3
  5. oxutils/constants.py +2 -0
  6. oxutils/context/__init__.py +0 -0
  7. oxutils/context/site_name_processor.py +11 -0
  8. oxutils/currency/__init__.py +0 -0
  9. oxutils/currency/admin.py +57 -0
  10. oxutils/currency/apps.py +7 -0
  11. oxutils/currency/controllers.py +79 -0
  12. oxutils/currency/enums.py +7 -0
  13. oxutils/currency/migrations/0001_initial.py +41 -0
  14. oxutils/currency/migrations/__init__.py +0 -0
  15. oxutils/currency/models.py +100 -0
  16. oxutils/currency/schemas.py +38 -0
  17. oxutils/currency/tests.py +3 -0
  18. oxutils/currency/utils.py +69 -0
  19. oxutils/functions.py +5 -2
  20. oxutils/logger/receivers.py +0 -2
  21. oxutils/oxiliere/__init__.py +0 -0
  22. oxutils/oxiliere/admin.py +3 -0
  23. oxutils/oxiliere/apps.py +6 -0
  24. oxutils/oxiliere/cacheops.py +7 -0
  25. oxutils/oxiliere/caches.py +33 -0
  26. oxutils/oxiliere/controllers.py +36 -0
  27. oxutils/oxiliere/enums.py +10 -0
  28. oxutils/oxiliere/management/__init__.py +0 -0
  29. oxutils/oxiliere/management/commands/__init__.py +0 -0
  30. oxutils/oxiliere/management/commands/init_oxiliere_system.py +86 -0
  31. oxutils/oxiliere/middleware.py +97 -0
  32. oxutils/oxiliere/migrations/__init__.py +0 -0
  33. oxutils/oxiliere/models.py +55 -0
  34. oxutils/oxiliere/permissions.py +104 -0
  35. oxutils/oxiliere/schemas.py +65 -0
  36. oxutils/oxiliere/settings.py +17 -0
  37. oxutils/oxiliere/tests.py +3 -0
  38. oxutils/oxiliere/utils.py +76 -0
  39. oxutils/pdf/__init__.py +10 -0
  40. oxutils/pdf/printer.py +81 -0
  41. oxutils/pdf/utils.py +94 -0
  42. oxutils/pdf/views.py +208 -0
  43. oxutils/settings.py +2 -0
  44. oxutils/users/__init__.py +0 -0
  45. oxutils/users/admin.py +3 -0
  46. oxutils/users/apps.py +6 -0
  47. oxutils/users/migrations/__init__.py +0 -0
  48. oxutils/users/models.py +88 -0
  49. oxutils/users/tests.py +3 -0
  50. oxutils/users/utils.py +15 -0
  51. {oxutils-0.1.2.dist-info → oxutils-0.1.6.dist-info}/METADATA +99 -11
  52. oxutils-0.1.6.dist-info/RECORD +88 -0
  53. {oxutils-0.1.2.dist-info → oxutils-0.1.6.dist-info}/WHEEL +1 -1
  54. oxutils/locale/fr/LC_MESSAGES/django.mo +0 -0
  55. oxutils-0.1.2.dist-info/RECORD +0 -45
@@ -0,0 +1,86 @@
1
+ import uuid
2
+ from django.core.management.base import BaseCommand
3
+ from django.conf import settings
4
+ from django.db import transaction
5
+ from django.contrib.auth import get_user_model
6
+ from django_tenants.utils import get_tenant_model
7
+ from oxutils.oxiliere.models import Domain, TenantUser
8
+ from oxutils.oxiliere.utils import oxid_to_schema_name
9
+
10
+
11
+
12
+ class Command(BaseCommand):
13
+ help = 'Initialise le tenant système Oxiliere'
14
+
15
+ @transaction.atomic
16
+ def handle(self, *args, **options):
17
+ TenantModel = get_tenant_model()
18
+ UserModel = get_user_model()
19
+
20
+ # Configuration du tenant système depuis settings
21
+ system_slug = getattr(settings, 'OXI_SYSTEM_TENANT', 'tenant_oxisystem')
22
+ schema_name = oxid_to_schema_name(system_slug)
23
+ system_domain = getattr(settings, 'OXI_SYSTEM_DOMAIN', 'system.oxiliere.com')
24
+ owner_email = getattr(settings, 'OXI_SYSTEM_OWNER_EMAIL', 'dev@oxiliere.com')
25
+ owner_oxi_id = uuid.uuid4()
26
+
27
+ self.stdout.write(self.style.WARNING(f'Initialisation du tenant système...'))
28
+
29
+ # Vérifier si le tenant système existe déjà
30
+ if TenantModel.objects.filter(schema_name=schema_name).exists():
31
+ self.stdout.write(self.style.ERROR(f'Le tenant système "{schema_name}" existe déjà!'))
32
+ return
33
+
34
+ # Créer le tenant système
35
+ self.stdout.write(f'Création du tenant système: {schema_name}')
36
+ tenant = TenantModel.objects.create(
37
+ name='Oxiliere System',
38
+ schema_name=schema_name,
39
+ oxi_id=system_slug,
40
+ subscription_plan='system',
41
+ subscription_status='active',
42
+ )
43
+ self.stdout.write(self.style.SUCCESS(f'✓ Tenant système créé: {tenant.name} ({tenant.schema_name})'))
44
+
45
+ # Créer le domaine pour le tenant système
46
+ self.stdout.write(f'Création du domaine: {system_domain}')
47
+ domain = Domain.objects.create(
48
+ domain=system_domain,
49
+ tenant=tenant,
50
+ is_primary=True
51
+ )
52
+ self.stdout.write(self.style.SUCCESS(f'✓ Domaine créé: {domain.domain}'))
53
+
54
+ self.stdout.write(f'Création du superuser: {owner_email}')
55
+ try:
56
+ superuser = UserModel.objects.get(email=owner_email)
57
+ self.stdout.write(self.style.WARNING(f'⚠ Superuser existe déjà: {superuser.email}'))
58
+ except UserModel.DoesNotExist:
59
+ superuser = UserModel.objects.create_superuser(
60
+ email=owner_email,
61
+ oxi_id=owner_oxi_id,
62
+ first_name='System',
63
+ last_name='Admin'
64
+ )
65
+ self.stdout.write(self.style.SUCCESS(f'✓ Superuser créé: {superuser.email}'))
66
+
67
+ # Lier le superuser au tenant système
68
+ self.stdout.write('Liaison du superuser au tenant système')
69
+ tenant_user, created = TenantUser.objects.get_or_create(
70
+ tenant=tenant,
71
+ user=superuser,
72
+ defaults={
73
+ 'is_owner': True,
74
+ 'is_admin': True,
75
+ }
76
+ )
77
+ if created:
78
+ self.stdout.write(self.style.SUCCESS(f'✓ Superuser lié au tenant système'))
79
+ else:
80
+ self.stdout.write(self.style.WARNING(f'⚠ Liaison existe déjà'))
81
+
82
+ self.stdout.write(self.style.SUCCESS('\n=== Initialisation terminée avec succès ==='))
83
+ self.stdout.write(f'Tenant: {tenant.name}')
84
+ self.stdout.write(f'Schema: {tenant.schema_name}')
85
+ self.stdout.write(f'Domain: {domain.domain}')
86
+ self.stdout.write(f'Superuser: {owner_email}')
@@ -0,0 +1,97 @@
1
+ from django.conf import settings
2
+ from django.db import connection
3
+ from django.http import Http404
4
+ from django.urls import set_urlconf
5
+ from django.utils.module_loading import import_string
6
+ from django.utils.deprecation import MiddlewareMixin
7
+
8
+ from django_tenants.utils import (
9
+ get_public_schema_name,
10
+ get_public_schema_urlconf
11
+ )
12
+ from oxutils.constants import ORGANIZATION_HEADER_KEY
13
+
14
+ class TenantMainMiddleware(MiddlewareMixin):
15
+ TENANT_NOT_FOUND_EXCEPTION = Http404
16
+ """
17
+ This middleware should be placed at the very top of the middleware stack.
18
+ Selects the proper database schema using the request host. Can fail in
19
+ various ways which is better than corrupting or revealing data.
20
+ """
21
+
22
+ @staticmethod
23
+ def get_org_id_from_request(request):
24
+ """ Extracts organization ID from request header X-Organization-ID.
25
+ """
26
+ custom = 'HTTP_' + ORGANIZATION_HEADER_KEY.upper().replace('-', '_')
27
+ return request.headers.get(ORGANIZATION_HEADER_KEY) or request.META.get(custom)
28
+
29
+ def get_tenant(self, tenant_model, oxi_id):
30
+ """ Get tenant by oxi_id instead of domain.
31
+ """
32
+ return tenant_model.objects.get(oxi_id=oxi_id)
33
+
34
+ def process_request(self, request):
35
+ # Connection needs first to be at the public schema, as this is where
36
+ # the tenant metadata is stored.
37
+
38
+ connection.set_schema_to_public()
39
+
40
+ oxi_id = self.get_org_id_from_request(request)
41
+ if not oxi_id:
42
+ from django.http import HttpResponseBadRequest
43
+ return HttpResponseBadRequest('Missing X-Organization-ID header')
44
+
45
+ tenant_model = connection.tenant_model
46
+ try:
47
+ tenant = self.get_tenant(tenant_model, oxi_id)
48
+ except tenant_model.DoesNotExist:
49
+ default_tenant = self.no_tenant_found(request, oxi_id)
50
+ return default_tenant
51
+
52
+ request.tenant = tenant
53
+ connection.set_tenant(request.tenant)
54
+ self.setup_url_routing(request)
55
+
56
+ def no_tenant_found(self, request, oxi_id):
57
+ """ What should happen if no tenant is found.
58
+ This makes it easier if you want to override the default behavior """
59
+ if hasattr(settings, 'DEFAULT_NOT_FOUND_TENANT_VIEW'):
60
+ view_path = settings.DEFAULT_NOT_FOUND_TENANT_VIEW
61
+ view = import_string(view_path)
62
+ if hasattr(view, 'as_view'):
63
+ response = view.as_view()(request)
64
+ else:
65
+ response = view(request)
66
+ if hasattr(response, 'render'):
67
+ response.render()
68
+ return response
69
+ elif hasattr(settings, 'SHOW_PUBLIC_IF_NO_TENANT_FOUND') and settings.SHOW_PUBLIC_IF_NO_TENANT_FOUND:
70
+ self.setup_url_routing(request=request, force_public=True)
71
+ else:
72
+ raise self.TENANT_NOT_FOUND_EXCEPTION('No tenant for X-Organization-ID "%s"' % oxi_id)
73
+
74
+ @staticmethod
75
+ def setup_url_routing(request, force_public=False):
76
+ """
77
+ Sets the correct url conf based on the tenant
78
+ :param request:
79
+ :param force_public
80
+ """
81
+ public_schema_name = get_public_schema_name()
82
+ if has_multi_type_tenants():
83
+ tenant_types = get_tenant_types()
84
+ if (not hasattr(request, 'tenant') or
85
+ ((force_public or request.tenant.schema_name == get_public_schema_name()) and
86
+ 'URLCONF' in tenant_types[public_schema_name])):
87
+ request.urlconf = get_public_schema_urlconf()
88
+ else:
89
+ tenant_type = request.tenant.get_tenant_type()
90
+ request.urlconf = tenant_types[tenant_type]['URLCONF']
91
+ set_urlconf(request.urlconf)
92
+
93
+ else:
94
+ # Do we have a public-specific urlconf?
95
+ if (hasattr(settings, 'PUBLIC_SCHEMA_URLCONF') and
96
+ (force_public or request.tenant.schema_name == get_public_schema_name())):
97
+ request.urlconf = settings.PUBLIC_SCHEMA_URLCONF
File without changes
@@ -0,0 +1,55 @@
1
+ from django.db import models
2
+ from django.conf import settings
3
+ from django_tenants.models import TenantMixin, DomainMixin
4
+ from oxutils.models import (
5
+ TimestampMixin,
6
+ BaseModelMixin,
7
+ )
8
+ from oxutils.oxiliere.enums import TenantStatus
9
+
10
+
11
+
12
+
13
+ class Tenant(TenantMixin, TimestampMixin):
14
+ name = models.CharField(max_length=100)
15
+ oxi_id = models.UUIDField(unique=True)
16
+ subscription_plan = models.CharField(max_length=255, null=True, blank=True)
17
+ subscription_status = models.CharField(max_length=255, null=True, blank=True)
18
+ subscription_end_date = models.DateTimeField(null=True, blank=True)
19
+ status = models.CharField(
20
+ max_length=20,
21
+ choices=TenantStatus.choices,
22
+ default=TenantStatus.ACTIVE
23
+ )
24
+
25
+ # default true, schema will be automatically created and synced when it is saved
26
+ auto_create_schema = True
27
+
28
+
29
+ class Domain(DomainMixin):
30
+ pass
31
+
32
+
33
+
34
+ class TenantUser(BaseModelMixin):
35
+ tenant = models.ForeignKey(
36
+ settings.TENANT_MODEL, on_delete=models.CASCADE
37
+ )
38
+ user = models.ForeignKey(
39
+ settings.AUTH_USER_MODEL, on_delete=models.CASCADE
40
+ )
41
+ is_owner = models.BooleanField(default=False)
42
+ is_admin = models.BooleanField(default=False)
43
+ status = models.CharField(
44
+ max_length=20,
45
+ choices=TenantStatus.choices,
46
+ default=TenantStatus.ACTIVE
47
+ )
48
+
49
+ class Meta:
50
+ constraints = [
51
+ models.UniqueConstraint(
52
+ fields=['tenant', 'user'],
53
+ name='unique_tenant_user'
54
+ )
55
+ ]
@@ -0,0 +1,104 @@
1
+ from ninja.permissions import BasePermission
2
+ from django.conf import settings
3
+ from oxutils.oxiliere.models import TenantUser
4
+
5
+
6
+ class TenantPermission(BasePermission):
7
+ """
8
+ Vérifie que l'utilisateur a accès au tenant actuel.
9
+ L'utilisateur doit être authentifié et avoir un lien avec le tenant.
10
+ """
11
+ def has_permission(self, request, view):
12
+ if not request.user or not request.user.is_authenticated:
13
+ return False
14
+
15
+ if not hasattr(request, 'tenant'):
16
+ return False
17
+
18
+ # Vérifier que l'utilisateur a accès à ce tenant
19
+ return TenantUser.objects.filter(
20
+ tenant=request.tenant,
21
+ user=request.user
22
+ ).exists()
23
+
24
+
25
+ class TenantOwnerPermission(BasePermission):
26
+ """
27
+ Vérifie que l'utilisateur est propriétaire (owner) du tenant actuel.
28
+ """
29
+ def has_permission(self, request, view):
30
+ if not request.user or not request.user.is_authenticated:
31
+ return False
32
+
33
+ if not hasattr(request, 'tenant'):
34
+ return False
35
+
36
+ # Vérifier que l'utilisateur est owner du tenant
37
+ return TenantUser.objects.filter(
38
+ tenant=request.tenant,
39
+ user=request.user,
40
+ is_owner=True
41
+ ).exists()
42
+
43
+
44
+ class TenantAdminPermission(BasePermission):
45
+ """
46
+ Vérifie que l'utilisateur est admin ou owner du tenant actuel.
47
+ """
48
+ def has_permission(self, request, view):
49
+ if not request.user or not request.user.is_authenticated:
50
+ return False
51
+
52
+ if not hasattr(request, 'tenant'):
53
+ return False
54
+
55
+ # Vérifier que l'utilisateur est admin ou owner du tenant
56
+ return TenantUser.objects.filter(
57
+ tenant=request.tenant,
58
+ user=request.user,
59
+ is_admin=True
60
+ ).exists() or TenantUser.objects.filter(
61
+ tenant=request.tenant,
62
+ user=request.user,
63
+ is_owner=True
64
+ ).exists()
65
+
66
+
67
+ class TenantUserPermission(BasePermission):
68
+ """
69
+ Vérifie que l'utilisateur est un membre du tenant actuel.
70
+ Alias de TenantPermission pour plus de clarté sémantique.
71
+ """
72
+ def has_permission(self, request, view):
73
+ if not request.user or not request.user.is_authenticated:
74
+ return False
75
+
76
+ if not hasattr(request, 'tenant'):
77
+ return False
78
+
79
+ return TenantUser.objects.filter(
80
+ tenant=request.tenant,
81
+ user=request.user
82
+ ).exists()
83
+
84
+
85
+ class OxiliereServicePermission(BasePermission):
86
+ """
87
+ Vérifie que la requête provient d'un service interne Oxiliere.
88
+ Utilise un token de service ou une clé API spéciale.
89
+ """
90
+ def has_permission(self, request, view):
91
+ # Vérifier le header de service
92
+ service_token = request.headers.get('X-Oxiliere-Service-Token')
93
+
94
+ if not service_token:
95
+ return False
96
+
97
+ # Comparer avec le token configuré dans settings
98
+ expected_token = getattr(settings, 'OXILIERE_SERVICE_TOKEN', None)
99
+
100
+ if not expected_token:
101
+ return False
102
+
103
+ return service_token == expected_token
104
+
@@ -0,0 +1,65 @@
1
+ from typing import Optional
2
+ from ninja import Schema
3
+ from django.db import transaction
4
+ from django.contrib.auth import get_user_model
5
+ from django_tenants.utils import get_tenant_model
6
+ from oxutils.oxiliere.models import TenantUser
7
+ from oxutils.oxiliere.utils import oxid_to_schema_name
8
+ import structlog
9
+
10
+ logger = structlog.get_logger(__name__)
11
+
12
+
13
+ class TenantSchema(Schema):
14
+ name: str
15
+ oxi_id: str
16
+ subscription_plan: Optional[str]
17
+ subscription_status: Optional[str]
18
+ subscription_end_date: Optional[str]
19
+ status: Optional[str]
20
+
21
+
22
+ class TenantOwnerSchema(Schema):
23
+ oxi_id: str
24
+ email: str
25
+
26
+
27
+ class CreateTenantSchema(Schema):
28
+ tenant: TenantSchema
29
+ owner: TenantOwnerSchema
30
+
31
+
32
+ @transaction.atomic
33
+ def create_tenant(self):
34
+ UserModel = get_user_model()
35
+ TenantModel = get_tenant_model()
36
+
37
+ if TenantModel.objects.filter(oxi_id=self.tenant.oxi_id).exists():
38
+ logger.info("tenant_exists", oxi_id=self.tenant.oxi_id)
39
+ raise ValueError("Tenant with oxi_id {} already exists".format(self.tenant.oxi_id))
40
+
41
+ user = UserModel.objects.get_or_create(
42
+ oxi_id=self.owner.oxi_id,
43
+ defaults={
44
+ 'email': self.owner.email,
45
+ }
46
+ )
47
+
48
+ tenant = TenantModel.objects.create(
49
+ name=self.tenant.name,
50
+ schema_name=oxid_to_schema_name(self.tenant.oxi_id),
51
+ oxi_id=self.tenant.oxi_id,
52
+ subscription_plan=self.tenant.subscription_plan,
53
+ subscription_status=self.tenant.subscription_status,
54
+ subscription_end_date=self.tenant.subscription_end_date,
55
+ )
56
+
57
+ TenantUser.objects.create(
58
+ tenant=tenant,
59
+ user=user,
60
+ is_owner=True,
61
+ is_admin=True,
62
+ )
63
+
64
+ logger.info("tenant_created", oxi_id=self.tenant.oxi_id)
65
+ return tenant
@@ -0,0 +1,17 @@
1
+ import os
2
+
3
+ TENANT_LIMIT_SET_CALLS = True
4
+
5
+ CACHEOPS_PREFIX = 'oxutils.oxiliere.cacheops.cacheops_prefix'
6
+
7
+
8
+ REDIS_URLS = os.getenv("REDIS_URLS", "redis://127.0.0.1:6379").split(",")
9
+
10
+ CACHES = {
11
+ "default": {
12
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
13
+ "LOCATION": REDIS_URLS,
14
+ 'KEY_FUNCTION': 'django_tenants.cache.make_key',
15
+ 'REVERSE_KEY_FUNCTION': 'django_tenants.cache.reverse_key',
16
+ }
17
+ }
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,76 @@
1
+ # utils.py
2
+
3
+ def oxid_to_schema_name(oxid: str) -> str:
4
+ """
5
+ Convertit un OXI-ID (slug) en nom de schéma PostgreSQL valide.
6
+
7
+ Règles PostgreSQL pour les noms de schéma:
8
+ - Doit commencer par une lettre (a-z) ou underscore (_)
9
+ - Peut contenir uniquement des lettres, chiffres et underscores
10
+ - Maximum 63 caractères
11
+ - Sensible à la casse mais conventionnellement en minuscules
12
+
13
+ Args:
14
+ oxid: Slug de l'organisation (ex: "my-company", "acme-corp")
15
+
16
+ Returns:
17
+ Nom de schéma valide (format: tenant_mycompany, tenant_acmecorp)
18
+ """
19
+ if not oxid:
20
+ raise ValueError("oxi_id cannot be empty")
21
+
22
+ # Nettoyer le slug: remplacer les tirets par underscores et convertir en minuscules
23
+ clean_id = str(oxid).replace('-', '_').lower()
24
+
25
+ # Supprimer tous les caractères non-alphanumériques sauf underscore
26
+ import re
27
+ clean_id = re.sub(r'[^a-z0-9_]', '', clean_id)
28
+
29
+ # Préfixer avec 'tenant_' pour s'assurer que ça commence par une lettre
30
+ # et pour éviter les conflits avec les schémas système de PostgreSQL
31
+ schema_name = f"tenant_{clean_id}"
32
+
33
+ # Vérifier la longueur (PostgreSQL limite à 63 caractères)
34
+ if len(schema_name) > 63:
35
+ raise ValueError(f"Schema name too long: {len(schema_name)} characters (max 63)")
36
+
37
+ return schema_name
38
+
39
+
40
+ def update_tenant_user(oxi_org_id: str, oxi_user_id: str, data: dict):
41
+ if not data or isinstance(data, dict) == False: return
42
+ if not oxi_org_id or not oxi_user_id: return
43
+
44
+ from oxutils.oxiliere.caches import get_tenant_user
45
+
46
+ TENANT_USER_FIELDS = ['is_owner', 'is_admin', 'status', 'is_active']
47
+ tenant_user = get_tenant_user(oxi_org_id, oxi_user_id)
48
+ changes = False
49
+
50
+ for key, value in data.items():
51
+ if key in TENANT_USER_FIELDS:
52
+ setattr(tenant_user, key, value)
53
+ changes = True
54
+
55
+ if changes:
56
+ tenant_user.save()
57
+
58
+
59
+ def update_tenant(oxi_id: str, data: dict):
60
+ if not data or isinstance(data, dict) == False: return
61
+ if not oxi_id: return
62
+
63
+ from oxutils.oxiliere.caches import get_tenant_by_oxi_id
64
+
65
+ TENANT_FIELDS = ['name', 'status', 'subscription_plan', 'subscription_status', 'subscription_end_date']
66
+
67
+ tenant = get_tenant_by_oxi_id(oxi_id)
68
+ changes = False
69
+
70
+ for key, value in data.items():
71
+ if key in TENANT_FIELDS:
72
+ setattr(tenant, key, value)
73
+ changes = True
74
+
75
+ if changes:
76
+ tenant.save()
@@ -0,0 +1,10 @@
1
+ from .views import WeasyTemplateResponse, WeasyTemplateResponseMixin, WeasyTemplateView
2
+ from .printer import Printer
3
+
4
+
5
+ __all__ = [
6
+ 'WeasyTemplateResponse',
7
+ 'WeasyTemplateResponseMixin',
8
+ 'WeasyTemplateView',
9
+ 'Printer',
10
+ ]
oxutils/pdf/printer.py ADDED
@@ -0,0 +1,81 @@
1
+ import weasyprint
2
+ from django.conf import settings
3
+ from django.template.loader import render_to_string
4
+
5
+ from oxutils.pdf.utils import django_url_fetcher
6
+
7
+
8
+ class Printer:
9
+ template_name = None
10
+ pdf_stylesheets = []
11
+ pdf_options = {}
12
+
13
+ def __init__(self, template_name=None, context=None, stylesheets=None, options=None, base_url=None):
14
+ self.template_name = template_name or self.template_name
15
+ self.context = context or {}
16
+ self._stylesheets = stylesheets or self.pdf_stylesheets
17
+ self._options = options.copy() if options else self.pdf_options.copy()
18
+ self._base_url = base_url
19
+
20
+ def get_context_data(self, **kwargs):
21
+ context = self.context.copy()
22
+ context.update(kwargs)
23
+ return context
24
+
25
+ def get_pdf_filename(self):
26
+ return None
27
+
28
+ def get_base_url(self):
29
+ if self._base_url:
30
+ return self._base_url
31
+ return getattr(settings, 'WEASYPRINT_BASEURL', '/')
32
+
33
+ def get_url_fetcher(self):
34
+ return django_url_fetcher
35
+
36
+ def get_font_config(self):
37
+ return weasyprint.text.fonts.FontConfiguration()
38
+
39
+ def get_css(self, base_url, url_fetcher, font_config):
40
+ return [
41
+ weasyprint.CSS(
42
+ value,
43
+ base_url=base_url,
44
+ url_fetcher=url_fetcher,
45
+ font_config=font_config,
46
+ )
47
+ for value in self._stylesheets
48
+ ]
49
+
50
+ def render_html(self, **kwargs):
51
+ context = self.get_context_data(**kwargs)
52
+ return render_to_string(self.template_name, context)
53
+
54
+ def get_document(self, **kwargs):
55
+ base_url = self.get_base_url()
56
+ url_fetcher = self.get_url_fetcher()
57
+ font_config = self.get_font_config()
58
+
59
+ html_content = self.render_html(**kwargs)
60
+ html = weasyprint.HTML(
61
+ string=html_content,
62
+ base_url=base_url,
63
+ url_fetcher=url_fetcher,
64
+ )
65
+
66
+ self._options.setdefault(
67
+ 'stylesheets',
68
+ self.get_css(base_url, url_fetcher, font_config),
69
+ )
70
+
71
+ return html.render(
72
+ font_config=font_config,
73
+ **self._options,
74
+ )
75
+
76
+ def write_pdf(self, output=None, **kwargs):
77
+ document = self.get_document(**kwargs)
78
+ return document.write_pdf(target=output, **self._options)
79
+
80
+ def write_object(self, file_obj, **kwargs):
81
+ return self.write_pdf(output=file_obj, **kwargs)