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)
Related
In my project, I have a main static folder and a sub folder named static. When I make changes in my sub folder named static (which I specified in COLLECTSTATIC_DIRS within the settings file), I save the file and run the collectstatic command.
This successfully saves the changes, however is really inefficient as I am constantly changing css and Javascript files inside my project, which I store as static files.
I browsed the web, and came across a solution named whitenoise, which is a pip package. But this package only works for a short period of time, and after a few times of closing and opening my project folder, it completely stopped working.
Does anybody have another solution to deal with this problem? Thank you.
You can use python-watchdog and write your own Django command:
import time
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
class Command(BaseCommand):
help = "Automatically calls collectstatic when the staticfiles get modified."
def handle(self, *args, **options):
event_handler = CollectstaticEventHandler()
observer = Observer()
for path in settings.STATICFILES_DIRS:
observer.schedule(event_handler, path, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
finally:
observer.stop()
observer.join()
class CollectstaticEventHandler(FileSystemEventHandler):
def on_moved(self, event):
super().on_moved(event)
self._collectstatic()
def on_created(self, event):
super().on_created(event)
self._collectstatic()
def on_deleted(self, event):
super().on_deleted(event)
self._collectstatic()
def on_modified(self, event):
super().on_modified(event)
self._collectstatic()
def _collectstatic(self):
call_command("collectstatic", interactive=False)
You can use 3rd party solution which doesn't belong to Django to monitor your files and run commands on the files changes.
Take a look at fswatch utility Bash Script - fswatch trigger bash function
How to manage static files "Django-way"
Please check details on https://docs.djangoproject.com/en/3.0/howto/static-files/ what is a Django-way to do it correct :)
First of all set DEBUG = True while working on development.
Then add these lines to your project's urls.py:
from django.conf import settings
from django.views.decorators.cache import cache_control
from django.contrib.staticfiles.views import serve
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL,
view=cache_control(no_cache=True, must_revalidate=True)(serve))
Here's an example using Python watchfiles and a Django management command.
# Lives in /my_app/manangement/commands/watch_files.py
import time
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand
from watchfiles import watch
class Command(BaseCommand):
help = "Automatically calls collectstatic when the staticfiles get modified."
def handle(self, *args, **options):
print('WATCH_STATIC: Static file watchdog started.')
#for changes in watch([str(x) for x in settings.STATICFILES_DIRS]):
for changes in watch(*settings.STATICFILES_DIRS):
print(f'WATCH_STATIC: {changes}', end='')
call_command("collectstatic", interactive=False)
You can then run the Django management command in the background in whatever script you use to start Django.
python manage.py watch_static &
python manage.py runserver 0.0.0.0:8000
I want to retrieve the list of all the possible directories where Django will search for template files. I would like to make an iterable list of those directories
I tried the following :
def get_template_directories():
template_directories = []
for template in settings.TEMPLATES:
for directory in template['DIRS']:
template_directories.append(directory)
return template_directories
But, I want all the directories where Django looks for template, including templates in INSTALLED_APPS
I searched in Django and couldn't find any helpful APIs to get all the directories containing templates in Django. I finally decided to write my code like the following:
def get_template_directories():
template_directories = []
# Getting templates in the templates directory
for template in settings.TEMPLATES:
for directory in template['DIRS']:
template_directories.append(directory)
# Getting templates from INSTALLED_APPS
for app in settings.INSTALLED_APPS:
path = join(apps.get_app_config(app.split('.')[-1]).path, 'templates')
if exists(path):
template_directories.append(path)
return template_directories
Your answer did not work for me, so I updated it to work with newer Django versions, as a management command:
""" Refer to help variable below
"""
from pathlib import Path
from django.apps import apps
from django.conf import settings
from django.core.management.base import BaseCommand
def get_template_directories(site_packages=False, verbose=False):
man_dirs = []
for template in settings.TEMPLATES:
man_dirs.extend(template['DIRS'])
app_dirs = []
for app in apps.get_app_configs():
path = f'{app.path}/templates'
Path(path).is_dir() and app_dirs.append(path)
app_dirs.sort()
if not site_packages:
man_dirs = [x for x in man_dirs if 'site-packages' not in str(x)]
app_dirs = [x for x in app_dirs if 'site-packages' not in str(x)]
if verbose:
print("\nManually specified dirs:\n" + "\n".join(str(x) for x in man_dirs))
print("\nApp template dirs:\n" + "\n".join(str(x) for x in app_dirs))
return man_dirs + app_dirs
class Command(BaseCommand):
help = "This prints a list of dirs inwhich templates are searched for"
requires_system_checks = False
def handle(self, **options):
get_template_directories(verbose=True)
I want to add the mobile version to my site, so I need to change the template path dynamically to get the mobile template dir or the desktop dir.
As the TEMPLATE_DIRS is deprecated since version 1.8, I tried to change the DIRS option of a DjangoTemplates backend, but it didn't work.
As I had the same challenge I post my code here. I am using the get_flavor from django_mobile to detect what to show and I wanted to use the standard admin directories (as installed) for django_admin_bootstrapped and the regular django admin. In addition I wanted to only interfere the loader with admin pages and not with regular pages - in the latter case the loader does nothing.
So this works for me:
import os.path
from django.template.loaders.base import Loader
from django.template.loader import LoaderOrigin
from django.template import TemplateDoesNotExist
from django_mobile import get_flavour
class Myloader(Loader):
is_usable = True #this line is necessary to make it work
def load_template_source(self, template_name, template_dirs=None):
path_split = template_name.split("/")
if not u'admin' in path_split:
raise TemplateDoesNotExist
template_short_name = path_split[-1]
if get_flavour() == "mobile":
basepath = r'/path/to/django_admin_bootstrapped/templates/admin'
path = os.path.join(basepath,template_short_name)
else:
basepath = r'/path/to/django/contrib/admin/templates/admin'
path = os.path.join(basepath,template_short_name)
try:
with open(path,"r") as f1:
template_string = f1.read()
except IOError:
raise TemplateDoesNotExist
template_origin = LoaderOrigin(template_short_name, self.load_template_source, template_name, template_dirs)
return (template_string, template_origin)
If you want to distinguish the template path by something different e.g. by the name in the url you need to replace the "if get_flavour()=="mobile" " by looking for something inntemplate_name.
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'),
)
I need to return css files and js files according to specific logic. Clearly, static serve does not perform what I need. I have a view, whose render method uses logic to find the proper file, but then I have to return it. Technically, I can just read the file and stuff it into a HttpResponse object with the proper mime type, but I was wondering if there was a better strategy. (like fpassthru() in php)
This is what I used:
test_file = open('/home/poop/serve/test.pdf', 'rb')
response = HttpResponse(content=test_file)
response['Content-Type'] = 'application/pdf'
response['Content-Disposition'] = 'attachment; filename="%s.pdf"' \
% 'whatever'
return response
What webserver software are you using?
At least for Apache and NginX, there is a module enabling you to use the X-SendFile HTTP header. The NginX website says Lighty can do this, too.
In your wrapper view:
...
abspath = '/most_secret_directory_on_the_whole_filesystem/protected_filename.css'
response = HttpResponse()
response['X-Sendfile'] = abspath
response['Content-Type'] = 'mimetype/submimetype'
# or let your webserver auto-inject such a header field
# after auto-recognition of mimetype based on filename extension
response['Content-Length'] = <filesize>
# can probably be left out if you don't want to hassle with getting it off disk.
# oh, and:
# if the file is stored via a models.FileField, you just need myfilefield.size
response['Content-Disposition'] = 'attachment; filename=%s.css' \
% 'whatever_public_filename_you_need_it_to_be'
return response
Then you can connect the view via http://mysite.com/url_path/to/serve_hidden_css_file/.
You can use it anytime you need to do something upon a file being requested that should not be directly accessible to users, like limiting who can access it, or counting requests to it for stats, or whatever.
For Apache: http://tn123.ath.cx/mod_xsendfile/
For NginX: http://wiki.nginx.org/NginxXSendfile
Why don't you use Django staticfiles inside your view
from django.contrib.staticfiles.views import serve
...
def view_function(request):
return serve(request, 'absolute_path_to_file_name')
Why not return an HttpResponseRedirect to the location of the correct static file?
Serving files directly from a view is very slow. If you are looking for normal file serving see this question: Having Django serve downloadable files
To very easily serve files through a view (for debug purposes, for example) keep reading.
# In your urls.py:
url(r'^test-files/(?P<name>.+)/$', views.test_files, name='test_files'),
# In your views.py:
from django.http.response import HttpResponse
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt # (Allows file download with POST requests, can be omitted)
def test_files(request, name):
if name == "myxml":
fsock = open("/djangopath/data/static/files/my.xml", "rb")
return HttpResponse(fsock)
This allows you to download the file from: http://127.0.0.1:8080/app/test-files/myxml/
Pass an iterator (such as the result of open()) to the HttpResponse constructor.
you can use below code in your view:
Note:in this function I return images but you can return every thing based your need and set your context_type
from django.http import HttpResponse,Http404
import os
def img_finder(request, img_name):
try:
with open(os.path.dirname(os.path.abspath(__file__)) + '/static/img/' + img_name, 'rb') as f:
return HttpResponse(f.read(), content_type="image/jpeg")
except IOError:
raise Http404
Here the most simple and efficient way to do this.
app/urls.py
from django.urls import re_path
from app import views
urlpatterns = [
re_path(r'^(?P<public_url>.*)$', views.public, name="public"),
]
Warning : put the URL pattern at the end
app/views.py
import os
from django.conf import settings
from django.views.static import serve
def public(request, public_url):
public_folder = os.path.join(str(settings.BASE_DIR), 'folder_path')
return serve(request, public_url, document_root=public_folder)
It should be wasteful to use django to serve static content (not to mention, several orders of magnitude slower).
I'd rather convert the view into a context processor and use the variables in templates to find what blocks to include.