django-compressor: Using lessc in DEBUG mode - django

I'm not sure I'm doing things right, but here's the issue:
I'm using django-compressor with the lessc preprocessor
Some of the LESS files have relative image URLs. Some are mine, some are 3rd party libraries (e.g. Bootstrap)
When COMPRESS_ENABLED is True, all is working fine
When COMPRESS_ENABLED is False, the CssAbsoluteFilter is not running anymore, which means all relative image URLs are kept relative and are therefore broken (since they're not relative from the CACHE directory)
I could come up with a "clever" directory structure where relative paths resolve to the same file whether they originate from the CACHE directory or from the LESS files directory, but that seems like a brittle workaround.
How do you usually work when it comes to LESS + django-compressor?

You can use a simple workaround:
COMPRESS_PRECOMPILERS = (
('text/less', 'path.to.precompilers.LessFilter'),
)
precompilers.py:
from compressor.filters.base import CompilerFilter
from compressor.filters.css_default import CssAbsoluteFilter
class LessFilter(CompilerFilter):
def __init__(self, content, attrs, **kwargs):
super(LessFilter, self).__init__(content, command='lessc {infile} {outfile}', **kwargs)
def input(self, **kwargs):
content = super(LessFilter, self).input(**kwargs)
return CssAbsoluteFilter(content).input(**kwargs)
Please note this works with both COMPRESS_ENABLED = True and False.

This has been fixed in django-compressor 1.6. From the changelog:
Apply CssAbsoluteFilter to precompiled css even when compression is disabled
i.e. the absolute filter is run on your less files even with DEBUG = True.

In case you're using django-libsass the filter code looks like this:
from compressor.filters.css_default import CssAbsoluteFilter
from django_libsass import SassCompiler
class PatchedSCSSCompiler(SassCompiler):
def input(self, **kwargs):
content = super(PatchedSCSSCompiler, self).input(**kwargs)
return CssAbsoluteFilter(content).input(**kwargs)
And then in your settings file:
COMPRESS_PRECOMPILERS = (
('text/x-scss', 'path.to.PatchedSCSSCompiler'),
)

Related

Django static files get from wrong url

I am migrating an old Django 2.2 project to Django 3.2.
I use AWS Cloudfront and S3.
I have done most of the tasks to do. But i have something weird in my static files serving.
When 'static' template tag is called, the rendered url is "https//<cloudfront_url>/static.." instead of "https://<cloudfront_url>/static..".
The comma disapears ! It obviously results to a 404 not found.
It worked without any problem on Django2.2.
So for the moment i dirty patched this by doing a '.replace("https//", "https://")' on django static template tag.
My settings relatives to static files are :
# settings.py
STATICFILES_LOCATION = 'static'
AWS_CLOUDFRONT_DOMAIN = <idcloudfront>.cloudfront.net
STATICFILES_STORAGE = 'app.custom_storages.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_CLOUDFRONT_DOMAIN, STATICFILES_LOCATION)
AWS_STORAGE_STATIC_BUCKET_NAME = 'assets'
# app.custom_storages.py
class StaticStorage(S3Boto3Storage):
location = settings.STATICFILES_LOCATION
bucket_name = settings.AWS_STORAGE_STATIC_BUCKET_NAME
def __init__(self, *args, **kwargs):
kwargs['custom_domain'] = settings.AWS_CLOUDFRONT_DOMAIN
super(StaticStorage, self).__init__(*args, **kwargs)
this seems to come from django storage since staticfiles_storage.url('css/a_css_file.css') return url without ":".
Oh ok after further investigations i see that come from storages.storages.backends.S3boto3.S3Boto3Storage from django-storages package.
https://github.com/jschneier/django-storages/blob/master/storages/backends/s3boto3.py#L577
url = '{}//{}/{}{}'.format(
self.url_protocol,
self.custom_domain,
filepath_to_uri(name),
'?{}'.format(urlencode(params)) if params else '',
)
":" seems to be missing.
Temporary fixed it by adding url_protocol = "https:" in my StaticStorage custom class and opened an issue on django-storage library.

Accessing .csv data from views file

I have a small Django app, and I'm trying to access data from a CSV file (static/blog/dat.csv, the static folder is at the same level as the templates folder and views.py; and everything is inside my blog app) so I can use it to plot a graph on the browser using Chart.js. Aside from not being able to do that, the app is working fine.
I know I'm gonna need to pass some sort of context to the view function, but I don't know how I'd do that. Also, I have a couple of similar csv files, and using them as static files in my app seems simpler and easier than to add everything to a database to access them that way.
# views.py
from django.shortcuts import render
from django.contrib.staticfiles.storage import staticfiles_storage
import csv
def rtest(request):
url = staticfiles_storage.url('blog/dat.csv')
with open(url, 'r') as csv_file:
csv_reader = csv.reader(csv_file)
for line in csv_reader:
context += line
return render(request, 'blog/r.html', context)
# urls.py
urlpatterns = [
# ...
path('r-test/', views.rtest, name='blog-r-test'),
]
Here is the error I'm getting:
FileNotFoundError at /r-test/
[Errno 2] No such file or directory: '/static/blog/dat.csv'
I'm sure this is not the only error.
I know that the way I'm using the context variable is wrong, but that's just to kind of show what I'm trying to do. If I could just print one cell from the csv, I would view this as a win. Please help, thank you!
------Edit1-------
After using staticfiles_storage.path() instead of staticfiles_storage.url()
ImproperlyConfigured at /r-test/
You're using the staticfiles app without having set the STATIC_ROOT setting to a filesystem path.
------Edit2------
I'm now able to find my csv file:
STATIC_URL = '/static/'
STATIC_ROOT = 'C:/Users/riccl/Documents/richie/Python/nuclear/main/book/static/book'
But my context variable still doesn't make any sense.
You need to use staticfiles_storage.path() to read the file. staticfiles_storage.url() will return the URL that a user will use to load the static file on your site
STATIC_ROOT is where all static files will be stored after you run collectstatic, most of the time this is set to <root of the project>/static/. This is also where staticfiles_storage.path() will look for static files.
You will also need to set STATICFILES_DIRS so that your file(s) can be found by collectstatic. I usually have a folder located at <root of the project>/<project name>/static/ which I add to STATICFILES_DIRS

how to manually lessc compile in django project?

I have a less file which looks like below
#static_url: "/static/"; //don't know how to use static tag or {{STATIC_URL}}
#import url("#{static_url}site_common/bower_components/bootstrap/less/bootstrap.less");
#import url("#{static_url}momsplanner/custom_bootstrap/custom-variables.less");
#import url("#{static_url}momsplanner/custom_bootstrap/custom-other.less");
#import url("#{static_url}site_common/bower_components/bootstrap/less/utilities.less");
It works fine, but when I try to compile it with lessc, it's doable but very messy (i'll have to do collectstatic first, and give the STATIC_ROOT as lessc's include-path option)
I guess using relative path in the above less file is easier than that, are there other alternatives?
I'd advise using relative #imports. I wrote a helper function to construct the include paths from the configured django INSTALLED_APPS since we're using AppDirectoriesFinder that you could adapt to your manual trans-compilation process (we use django-compressor):
from compressor.filters.base import CompilerFilter
from django.utils.functional import memoize
_static_locations = {}
def _get_static_locations():
from django.conf import settings
"""
Captures all the static dirs (both from filesystem and apps) for build an include path for the LESS compiler.
"""
dirs = ['.']
for dir in settings.STATICFILES_DIRS:
dirs.append(dir)
for app in settings.INSTALLED_APPS:
from django.utils.importlib import import_module
import os.path
mod = import_module(app)
mod_path = os.path.dirname(mod.__file__)
location = os.path.join(mod_path, "static")
if os.path.isdir(location):
dirs.append(location)
return dirs
get_static_locations = memoize(_get_static_locations, _static_locations, 0)
class LessCompilerFilter(CompilerFilter):
def __init__(self, content, command=None, *args, **kwargs):
command = 'lessc --no-color --include-path=%s {infile} {outfile}' % ':'.join(get_static_locations())
super(LessCompilerFilter, self).__init__(content, command, *args, **kwargs)

How to specify media bundles per application in django-mediagenerator?

Django-mediagenerator has been pretty helpful so far, but thing that has been bothering me is that all MEDIA_BUNDLES are defined in settings.py. I would like to be able to define media bundles inside individual application folders.
How can media bundles be separated by application, instead of all being centralized in settings.py?
The only way I've found to do this is to do it yourself. I ended up writing a small file in same folder as my settings file:
from settings import MEDIA_BUNDLES, INSTALLED_APPS
BUNDLE_NAME = 'MEDIA_BUNDLES' # Name of bundles tuple
BUNDLE_FILE = 'media' # Name of python file
for app_name in INSTALLED_APPS:
if app_name.startswith('apps'):
try:
path = '{app}.{media}'.format(app=app_name, media=BUNDLE_FILE)
app = __import__(path, None, None, [BUNDLE_NAME])
bundles = getattr(app, BUNDLE_NAME)
MEDIA_BUNDLES = MEDIA_BUNDLES + bundles
except Exception, e:
print e

TemplateDoesNotExist - file exists, no permissions issue

I'm receiving this error when trying to render a template in django:
TemplateDoesNotExist
...
Template-loader postmortem
Django tried loading these templates, in this order:
Using loader django.template.loaders.filesystem.Loader:
Using loader django.template.loaders.app_directories.Loader:
Here's my settings.py entry:
SETTINGS_PATH = os.path.normpath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
os.path.join(SETTINGS_PATH, 'template'),
)
Here's my view that's being called:
def handler(request):
if request.method == 'GET':
NewAppFormSet = modelformset_factory(Application)
return render_to_response(request, "template/apps.html",{"new_app_form":NewAppFormSet})
I've tried every possible combination of paths, adjusting permissions, etc. The real issue seems to be with the template loaders, as they are not recognizing any paths set forth in TEMPLATE_DIRS. I've hardcoded this string, to no avail. I've also ran python manage.py runserver with sudo / as root. I'm at a loss...
I had a very similar issue which was driving me crazy. It turns out that the book I was using to learn Python/Django did not say that I had to set the templates directory in settings.py expressly. Very confusing. I am using Python 2.6 & Django 1.3.1 - maybe a previous version automatically found the templates dir. Anyway, after pulling my hair out for a day and reading a lot on this site I figured out that I had to replace the default TEMPLATE_DIRS assignment with the following in settings.py:
# Find templates in the same folder as settings.py.
SETTINGS_PATH = os.path.realpath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(SETTINGS_PATH, 'templates'),
)
Thanks stackoverflow!!
I'm not sure what example I was following that specified the render_to_response shortcut method requires the views' request object to be passed, but it was as easy as changing it to:
return render_to_response("template/apps.html",{"new_app_form":NewAppFormSet()})
Try assigning SETTINGS_PATH with os.path.realpath:
SETTINGS_PATH = os.path.realpath(os.path.dirname(__file__))
By any chance, you might have copy-pasted the TEMPLATE_DIRS you have now, did you leave the default one lower down the SETTINGS file?
From my settings.py:
BASE_DIR = os.path.dirname(__file__)
def RELATIVE_PATH(*args):
return os.path.join(BASE_DIR, *args)
TEMPLATE_DIRS = (
RELATIVE_PATH('template'),
)
what if you try
return render_to_response(request, "apps.html",{"new_app_form":NewAppFormSet})
instead of
return render_to_response(request, "template/apps.html",{"new_app_form":NewAppFormSet})