11import base64
2+ import html
23import logging
34import pathlib
45import uuid
6+ from html .parser import HTMLParser
57
68from django .conf import settings
79from django .templatetags .static import static
810from django .utils .functional import cached_property
911from django .utils .html import format_html , html_safe
12+ from django .utils .safestring import mark_safe
1013from storages .utils import safe_join
1114
1215from s3file .middleware import S3FileMiddleware
1518logger = logging .getLogger ("s3file" )
1619
1720
21+ class InputToS3FileRewriter (HTMLParser ):
22+ """HTML parser that rewrites <input type="file"> to <s3-file> custom elements."""
23+
24+ def __init__ (self ):
25+ super ().__init__ ()
26+ self .output = []
27+
28+ def handle_starttag (self , tag , attrs ):
29+ if tag == "input" and dict (attrs ).get ("type" ) == "file" :
30+ self .output .append ("<s3-file" )
31+ for name , value in attrs :
32+ if name != "type" :
33+ self .output .append (
34+ f' { name } ="{ html .escape (value , quote = True )} "'
35+ if value
36+ else f" { name } "
37+ )
38+ self .output .append (">" )
39+ else :
40+ self .output .append (self .get_starttag_text ())
41+
42+ def handle_endtag (self , tag ):
43+ self .output .append (f"</{ tag } >" )
44+
45+ def handle_data (self , data ):
46+ self .output .append (data )
47+
48+ def handle_startendtag (self , tag , attrs ):
49+ if tag == "input" and dict (attrs ).get ("type" ) == "file" :
50+ self .output .append ("<s3-file" )
51+ for name , value in attrs :
52+ if name != "type" :
53+ self .output .append (
54+ f' { name } ="{ html .escape (value , quote = True )} "'
55+ if value
56+ else f" { name } "
57+ )
58+ self .output .append (">" )
59+ else :
60+ self .output .append (self .get_starttag_text ())
61+
62+ def handle_comment (self , data ):
63+ # Preserve HTML comments in the output
64+ self .output .append (f"<!--{ data } -->" )
65+
66+ def handle_decl (self , decl ):
67+ # Preserve declarations such as <!DOCTYPE ...> in the output
68+ self .output .append (f"<!{ decl } >" )
69+
70+ def handle_pi (self , data ):
71+ # Preserve processing instructions such as <?xml ...?> in the output
72+ self .output .append (f"<?{ data } >" )
73+
74+ def handle_entityref (self , name ):
75+ # Preserve HTML entities like &, <, >
76+ self .output .append (f"&{ name } ;" )
77+
78+ def handle_charref (self , name ):
79+ # Preserve character references like ', '
80+ self .output .append (f"&#{ name } ;" )
81+
82+ def get_html (self ):
83+ return "" .join (self .output )
84+
85+
1886@html_safe
1987class Asset :
2088 """A generic asset that can be included in a template."""
@@ -75,7 +143,6 @@ def client(self):
75143
76144 def build_attrs (self , * args , ** kwargs ):
77145 attrs = super ().build_attrs (* args , ** kwargs )
78- attrs ["is" ] = "s3-file"
79146
80147 accept = attrs .get ("accept" )
81148 response = self .client .generate_presigned_post (
@@ -97,6 +164,13 @@ def build_attrs(self, *args, **kwargs):
97164
98165 return defaults
99166
167+ def render (self , name , value , attrs = None , renderer = None ):
168+ """Render the widget as a custom element for Safari compatibility."""
169+ html_output = str (super ().render (name , value , attrs = attrs , renderer = renderer ))
170+ parser = InputToS3FileRewriter ()
171+ parser .feed (html_output )
172+ return mark_safe (parser .get_html ()) # noqa: S308
173+
100174 def get_conditions (self , accept ):
101175 conditions = [
102176 {"bucket" : self .bucket_name },
0 commit comments