1+ from collections import defaultdict
2+ import importlib
3+ import os
14import warnings
25
6+ from django .apps import apps
7+ from django .template import loader
38from django .utils .module_loading import autodiscover_modules
49
10+ from djangocms_frontend import settings
11+ from djangocms_frontend .component_base import CMSFrontendComponent
12+
13+
14+ def find_cms_component_templates () -> list [str ]:
15+ templates = []
16+ for app in apps .get_app_configs ():
17+ app_template_dir = os .path .join (app .path , "templates" , app .label , "cms_components" )
18+ if os .path .exists (app_template_dir ):
19+ for root , _ , files in os .walk (app_template_dir ):
20+ for file in files :
21+ if file .endswith (".html" ) or file .endswith (".htm" ):
22+ relative_path = os .path .relpath (os .path .join (root , file ), app_template_dir )
23+ templates .append (f"{ app .label } /cms_components/{ relative_path } " )
24+ return templates
25+
26+
27+ class CMSAutoComponentDiscovery :
28+ default_field_context = {
29+ "djanghocms_text" : "djangocms_text.fields.TextFormField" ,
30+ "djanghocms_text_ckeditor" : "djangocms_text_ckeditor.fields.TextFormField" ,
31+ "djangocms_link" : "djangocms_link.fields.LinkFormField" ,
32+ "djangocms_frontend.contrib.image" : "djangocms_frontend.contrib.image.fields.ImageFormField" ,
33+ "djangocms_frontend.contrib.icon" : "djangocms_frontend.contrib.icon.fields.IconPickerField" ,
34+ }
35+
36+ def __init__ (self , register_to ):
37+ templates = find_cms_component_templates ()
38+ auto_components = self .scan_templates_for_component_declaration (templates )
39+ for component in auto_components :
40+ register_to .register (component )
41+
42+ def get_field_context (self ) -> dict :
43+ field_context = {}
44+ self .default_field_context .update (settings .COMPONENT_FIELDS )
45+ for key , value in self .default_field_context .items ():
46+ if apps .is_installed (key ) and "." in value :
47+ module , field_name = value .rsplit ("." , 1 )
48+ field_context [field_name ] = importlib .import_module (module ).__dict__ [field_name ]
49+ return field_context
50+
51+ @staticmethod
52+ def component_factory (component : tuple , fields : list [tuple ], template : str ) -> CMSFrontendComponent :
53+ args , kwargs = component
54+ (name ,) = args
55+
56+ kwargs ["render_template" ] = template
57+ meta = type ("Meta" , (), kwargs )
58+ cls = type (
59+ name ,
60+ (CMSFrontendComponent ,),
61+ {
62+ "Meta" : meta ,
63+ "__module__" : "djangocms_frontend.contrib.auto_component.cms_components" ,
64+ ** {
65+ # Django template engine instantiates objects -- re-instantiate them here
66+ args [0 ]: args [1 ].__class__ (** kwargs )
67+ for args , kwargs in fields
68+ },
69+ },
70+ )
71+ return cls
72+
73+ def scan_templates_for_component_declaration (self , templates : list [str ]) -> list [CMSFrontendComponent ]:
74+ from django .forms import fields
75+
76+ components = []
77+ field_context = self .get_field_context ()
78+ for template_name in templates :
79+ context = {"_cms_components" : defaultdict (list ), "forms" : fields , "instance" : {}, ** field_context }
80+ loader .render_to_string (template_name , context )
81+ cms_component = context ["_cms_components" ].get ("cms_component" , [])
82+ fields = context ["_cms_components" ].get ("fields" , [])
83+ if len (cms_component ) == 1 :
84+ components .append (self .component_factory (cms_component [0 ], fields , template_name ))
85+ elif len (cms_component ) > 1 :
86+ raise ValueError (f"Multiple cms_component tags found in { template_name } " )
87+ return components
88+
589
690class Components :
791 _registry : dict = {}
@@ -20,6 +104,16 @@ def __getitem__(self, item):
20104
21105components = Components ()
22106
23- if not components ._discovered :
24- autodiscover_modules ("cms_components" , register_to = components )
25- components ._discovered = True
107+
108+ def setup ():
109+ global components
110+
111+ if not components ._discovered :
112+ from .cms_plugins import update_plugin_pool
113+
114+ # First discover components in cms_components module
115+ autodiscover_modules ("cms_components" , register_to = components )
116+ # The discover auto components by their templates
117+ CMSAutoComponentDiscovery (register_to = components )
118+ update_plugin_pool ()
119+ components ._discovered = True
0 commit comments