Repository: django-compressor/django-compressor

Issue:
TypeError Exception using TemplateFilter as Precompiler
```
COMPRESS_PRECOMPILERS = (
    ('text/x-scss', 'sass --scss {infile} {outfile}'),
    ('text/django', 'compressor.filters.template.TemplateFilter'),
)
```

```
{% compress css %}
    <link rel="stylesheet" href="foo.css" />
    <link type="text/django" rel="stylesheet" href="bar.css.djt" />
{% endcompress %}
```

Raises a TypeError:

```
Exception Type: TypeError
Exception Value:    
__init__() got multiple values for keyword argument 'filter_type'
```

According to the traceback, it seems that an "attrs" variable is passed to the filter.  However, compressor.filters.base.FilterBase does not accept the attrs positional argument.  See https://github.com/django-compressor/django-compressor/blob/develop/compressor/filters/base.py#L27 and https://github.com/django-compressor/django-compressor/blob/develop/compressor/base.py#L262

I think this is causing the issue.



Discussion:
That was the issue.  I quickly copied it into a new file as a temporary workaround.  Pasted below:

```
import logging
from django.template import Template, Context
from django.conf import settings

logger = logging.getLogger("foo.compressor_precompilers")

class FilterBase(object):
    """
    A base class for filters that does nothing.

    Subclasses should implement `input` and/or `output` methods which must
    return a string (unicode under python 2) or raise a NotImplementedError.
    """
    def __init__(self, content, filter_type=None, filename=None, verbose=0,
                 charset=None):
        self.type = filter_type
        self.content = content
        self.verbose = verbose or settings.COMPRESS_VERBOSE
        self.logger = logger
        self.filename = filename
        self.charset = charset

    def input(self, **kwargs):
        raise NotImplementedError

    def output(self, **kwargs):
        raise NotImplementedError

class TemplateFilter(FilterBase):

    def input(self, filename=None, basename=None, **kwargs):
        template = Template(self.content)
        context = Context(settings.COMPRESS_TEMPLATE_FILTER_CONTEXT)
        return template.render(context)

class TemplateFilterPrecompiler(TemplateFilter):
    def __init__(self, *args, **kwargs):
        args = list(args)
        attrs = args.pop()
        if 1 < len(args):
            raise TypeError("Precompiler is only written to handle the extra 'attrs' argument.  See https://github.com/django-compressor/django-compressor/issues/533")
        super(TemplateFilterPrecompiler, self).__init__(*args, **kwargs)
```

I have this same issue. 

The issue was introduced by this commit 09dc8498cd4cf14799c676b68bb5ed81509f9336

The comment @matthewwithanm made on the commit raises further issue with the commit.

I think this commit actually broke the functionality advertised by the [documentation](http://django-compressor.readthedocs.org/en/latest/settings/#django.conf.settings.COMPRESS_PRECOMPILERS). As observed by @matthewwithanm The call no longer matches the signature of BaseFilter.

My temporary workaround: 

```
from compressor.filters.template import TemplateFilter

class PatchTemplateFilter(TemplateFilter):
    def __init__(self, content, attrs, **kwargs):
        super(PatchTemplateFilter, self).__init__(content, **kwargs)
```

+1


Resolving patch:
diff --git a/compressor/base.py b/compressor/base.py
--- a/compressor/base.py
+++ b/compressor/base.py
@@ -276,7 +276,7 @@ def precompile(self, content, kind=None, elem=None, filename=None,
         except AttributeError:
             raise FilterDoesNotExist('Could not find "%s".' % filter_or_command)
         filter = precompiler_class(
-            content, attrs, filter_type=self.type, charset=charset,
+            content, attrs=attrs, filter_type=self.type, charset=charset,
             filename=filename)
         return True, filter.input(**kwargs)
 
diff --git a/compressor/filters/base.py b/compressor/filters/base.py
--- a/compressor/filters/base.py
+++ b/compressor/filters/base.py
@@ -44,8 +44,8 @@ class FilterBase(object):
     Subclasses should implement `input` and/or `output` methods which must
     return a string (unicode under python 2) or raise a NotImplementedError.
     """
-    def __init__(self, content, filter_type=None, filename=None, verbose=0,
-                 charset=None):
+    def __init__(self, content, attrs=None, filter_type=None, filename=None,
+                 verbose=0, charset=None, **kwargs):
         self.type = filter_type or getattr(self, 'type', None)
         self.content = content
         self.verbose = verbose or settings.COMPRESS_VERBOSE
@@ -118,8 +118,8 @@ class CompilerFilter(FilterBase):
     options = ()
     default_encoding = settings.FILE_CHARSET
 
-    def __init__(self, content, command=None, *args, **kwargs):
-        super(CompilerFilter, self).__init__(content, *args, **kwargs)
+    def __init__(self, content, command=None, **kwargs):
+        super(CompilerFilter, self).__init__(content, **kwargs)
         self.cwd = None
 
         if command:
@@ -216,9 +216,9 @@ def input(self, **kwargs):
 
 class CachedCompilerFilter(CompilerFilter):
 
-    def __init__(self, mimetype, **kwargs):
+    def __init__(self, mimetype, *args, **kwargs):
         self.mimetype = mimetype
-        super(CachedCompilerFilter, self).__init__(**kwargs)
+        super(CachedCompilerFilter, self).__init__(*args, **kwargs)
 
     def input(self, **kwargs):
         if self.mimetype in settings.COMPRESS_CACHEABLE_PRECOMPILERS:


Test patch:
diff --git a/compressor/tests/test_base.py b/compressor/tests/test_base.py
--- a/compressor/tests/test_base.py
+++ b/compressor/tests/test_base.py
@@ -282,6 +282,15 @@ def test_command_with_dot_precompiler(self):
         css_node = CssCompressor(css)
         self.assertRaises(FilterError, css_node.output, 'inline')
 
+    @override_settings(COMPRESS_PRECOMPILERS=(
+        ('text/django', 'compressor.filters.template.TemplateFilter'),
+    ), COMPRESS_ENABLED=True)
+    def test_template_precompiler(self):
+        css = '<style type="text/django">p { border:10px solid {% if 1 %}green{% else %}red{% endif %};}</style>'
+        css_node = CssCompressor(css)
+        output = make_soup(css_node.output('inline'))
+        self.assertEqual(output.text, 'p { border:10px solid green;}')
+
 
 class CssMediaTestCase(SimpleTestCase):
     def setUp(self):