How do I ignore static files of a particular app only with collectstatic? - django

Just to get this out of the way, if at all possible, I'd like to do this without nesting them all inside a directory with the app's name inside the app's static folder, it feels redundant. If it's the only way then such is life.
I am using:
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
)
and:
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
which compiles the JS and SASS when running collectstatic. These are both located in the APP_ROOT/static/ directory.
The only 'problem' is that it brings along all the source sass and js files alongside it. At some point I'm going to be pushing this all to S3 and I'd like to avoid that if possible.
I found that if you run:
python manage.py collectstatic -i sass -i js
It still compiles the JS and CSS files I specified, while leaving the rest of the 'source' files out. Unfortunately, it also ignores every js file in /admin/ as it matches /admin/js/ etc. I don't even know if that's likely to be a problem for this particular project, but I can foresee in the future other apps where I definitely will want to include static js/css kept in an app.
What I'd like to be able to do is something like:
python manage.py collectstatic -i app_name/sass -i app_name/js
And as I mentioned at the top the easy solution is just to prefix my static files in the folder with app_name/, much like how django.contrib.admin does it. At this point however, you end up with a directory structure of PROJECT_ROOT/app_name/static/app_name/[js|sass|img|data]/ and I think it should be possible to avoid the redundancy.
Then again, maybe it's the best option, so as to guarantee avoiding conflict with other apps?
I've looked into writing custom storages and finders, and I think it's possible to roll my own. I wanted to check here though first, to see if this is a problem someone else has solved, or, to get a reality check if the overwhelming response is to just add the prefix directory.
If I was to roll my own, the path I think I would take would be extending django.contrib.staticfiles.finders.AppDirectoriesFinder and overriding list(). I'm not yet positive this approach would work, I need to more trace how things progress from the collectstatic management command, so if anyone has done this or something simlilar before, or knows why it will/won't work, any help is appreciated.
Thanks!

I managed to solve this by subclassing Django finders like this:
PYTHON 2.X
from django.contrib.staticfiles import finders
from django.conf import settings
def add_ignores(ignore_patterns):
ignore = settings.STATICFILES_FINDERS_IGNORE
if ignore:
if ignore_patterns:
ignore_patterns.extend(ignore)
else:
ignore_patterns = ignore
return ignore_patterns
class FileSystemFinderIgnore(finders.FileSystemFinder):
def list(self, ignore_patterns):
return super(FileSystemFinderIgnore, self).list(add_ignores(ignore_patterns))
class AppDirectoriesFinderIgnore(finders.AppDirectoriesFinder):
def list(self, ignore_patterns):
return super(AppDirectoriesFinderIgnore, self).list(add_ignores(ignore_patterns))
class DefaultStorageFinderIgnore(finders.DefaultStorageFinder):
def list(self, ignore_patterns):
return super(DefaultStorageFinderIgnore, self).list(add_ignores(ignore_patterns))
PYTHON 3.X
from django.contrib.staticfiles import finders
from django.conf import settings
def add_ignores(ignore_patterns):
ignore = settings.STATICFILES_FINDERS_IGNORE
if ignore:
if ignore_patterns:
ignore_patterns.extend(ignore)
else:
ignore_patterns = ignore
return ignore_patterns
class FileSystemFinderIgnore(finders.FileSystemFinder):
def list(self, ignore_patterns):
return super().list(add_ignores(ignore_patterns))
class AppDirectoriesFinderIgnore(finders.AppDirectoriesFinder):
def list(self, ignore_patterns):
return super().list(add_ignores(ignore_patterns))
class DefaultStorageFinderIgnore(finders.DefaultStorageFinder):
def list(self, ignore_patterns):
return super().list(add_ignores(ignore_patterns))
and adding this to my settings:
STATICFILES_FINDERS_IGNORE = [
'*.scss',
'*.js',
]

I'm new to django-pipeline, but I believe it now has pipeline.finders.FileSystemFinder and pipeline.finders.AppDirectoriesFinder to do exactly this.
See the section "If you want to exclude Pipelinable content from your collected static files" on https://django-pipeline.readthedocs.org/en/latest/storages.html.
Also, from the source code of 1.5:
class AppDirectoriesFinder(PatternFilterMixin, DjangoAppDirectoriesFinder):
"""
Like AppDirectoriesFinder, but doesn't return any additional ignored
patterns.
This allows us to concentrate/compress our components without dragging
the raw versions in via collectstatic.
"""
Note that at the time of writing, this results in empty compressed/minified content when DEBUG==True, see https://github.com/cyberdelia/django-pipeline/issues/418. I assume this will be fixed in future versions of django-pipeline.

Related

How to customize TRAC plugin template python file

I am currently modifying our TRAC instance to Bootstrap 3.1. However, some templating needs to be done on the .py files. I only know how to customize .html files... just add classes, customize DOM structure a little bit then put it in templates folder of our TRAC instance.
NOW WHAT ABOUT customizing .py files from plugins? I tried putting them in templates folder but nothing happened.
I had no experience with Python, but it's easy just to hack around and add a bootstrap class e.g adding "col-sm-2 control-label" in a label in milestone.py
def __edit_project(self, data, req):
milestone = data.get('milestone').name
all_projects = self.__SmpModel.get_all_projects_filtered_by_conditions(req)
id_project_milestone = self.__SmpModel.get_id_project_milestone(milestone)
if id_project_milestone != None:
id_project_selected = id_project_milestone[0]
else:
id_project_selected = None
return tag.div(
tag.label(
class_="col-sm-2 control-label",
'Project',
tag.br(),
tag.select(
tag.option(),
[tag.option(row[1], selected=(id_project_selected == row[0] or None), value=row[0]) for row in sorted(all_projects, key=itemgetter(1))],
name="project")
),
class_="field")
Compiling the plugin again worked for me. After adding bootstrap classes on specific .py files, here are the steps/commands I did:
In our TRAC environment plugins directory where specific setup.py of the plugin I'm editing is located, build the .egg file e.g
tracproject/plugins_source/sampleplugin: python setup.py bdist_egg
Then I renamed the plugin's original .egg file in the plugins directory e.g
tracproject/plugins/sampleplugin/: mv sampleplugin.egg sampleplugin.egg.old
After that, I copied the newly .egg file generated to the plugins directory e.g
tracproject/plugins_source/sampleplugin/dist: mv sampleplugin.egg ../../../plugins/
Lastly, I restarted our server e.g (however, there were cases, no restart was needed since changes were instantly reflected)
sudo service apache2 restart
Thanks #falkb! I see that you're the author of SimpleMultiProject plugin I was trying to put bootstrap classes. :)
Here's a snippet of simplemultiprojectplugin milestone.py where I added styling
def __edit_project(self, data, req):
milestone = data.get('milestone').name
all_projects = self.__SmpModel.get_all_projects_filtered_by_conditions(req)
id_project_milestone = self.__SmpModel.get_id_project_milestone(milestone)
if id_project_milestone != None:
id_project_selected = id_project_milestone[0]
else:
id_project_selected = None
return tag.div(
tag.label('Project', class_="control-label col-sm-2"),
tag.div(
tag.select(
tag.option(),
[tag.option(row[1], selected=(id_project_selected == row[0] or None), value=row[0]) for row in sorted(all_projects, key=itemgetter(1))],
name="project",
class_="form-control"),
class_="col-sm-5"),
class_="form-group")

Django Static Files: Any Way to Get More Fine-Grained Control?

Django's static files feature allows you to specify certain directories to have "collected" in to a public-facing folder. That's great, but is there any way to get more fine-grained control than just having certain folders? For instance, is there anyway to specify ...
Including specific files
Excluding specific files
Excluding specific sub-directories
For instance, I'd like to say "collect all the files in this one folder except for this one file and this one directory". Alternatively, I could accomplish the same thing if I could pick specific files, and then pick all of the sub-directories of that one directory (except the one I don't want).
Is any of that possible?
I wrote a custom django-admin command to enable a COLLECT_STATIC_IGNORE setting.
First create the following stucture in any app folder:
appname/
management/
__init__.py
commands/
__init__.py
_private.py
collectstatic.py
In collectstatic.py put:
from django.contrib.staticfiles.management.commands.collectstatic import Command
from django.conf import settings
class Command(Command):
def set_options(self, **options):
"""
Set instance variables based on an options dict
"""
self.interactive = options['interactive']
self.verbosity = int(options.get('verbosity', 1))
self.symlink = options['link']
self.clear = options['clear']
self.dry_run = options['dry_run']
ignore_patterns = options['ignore_patterns']
if options['use_default_ignore_patterns']:
ignore_patterns += ['CVS', '.*', '*~']
ignore_patterns += settings.COLLECT_STATIC_IGNORE # Added.
self.ignore_patterns = list(set(ignore_patterns))
self.post_process = options['post_process']
Or, even better, like #CantucciHQ suggested, use super:
class Command(Command):
def set_options(self, **options):
super(Command, self).set_options(**options)
self.ignore_patterns += settings.COLLECT_STATIC_IGNORE
self.ignore_patterns = list(set(self.ignore_patterns))
This overrides the set_options function from de build-in collectstatic command.
In settings.py add COLLECT_STATIC_IGNORE.
This example ignores scss files and all files in admin folders.
COLLECT_STATIC_IGNORE = ['*.scss', 'admin', ... ]
Then:
python manage.py collectstatic
Flags work so after adding something to COLLECT_STATIC_IGNORE you might want to use --clear to clear the existing files before trying to copy or link the original file.
python manage.py collectstatic --clear

Importing variables from other py files

I have a .py file that contains database settings (call it connection.py). I am trying to import the variables contained in my connection.py into my views.py. However, the variables are not being imported.
In my views.py I have:
from connection import *
def location_where_connection_variables_needed(request, some_id):
conn = psycopg2.connect(conn_string)
cursor = conn.cursor()
I then use the variables in connection.py in my views.py script. But the variables are not being imported into my views.py. I placed connection.py in the main folder (ie, connection.py is in the same folder as my app's folder). Is this the correct place to put this? Or am I doing something else fundamentally wrong?
Edit:
What I mean by not being imported:
I am not getting any error at all but the variables are not being imported because the code in views.py is not executing. If I add the connection variable manually in my views.py, everything works fine.
Connection.py
conn_string = "host='localhost' dbname='test' user='postgres' password='pwd'"
With constant values, I typically put those into a file called constants.py and specify those values in all caps, which is a Python convention. So, I would replace "connection.py" with "constants.py" as such:
# constants.py
CONN_STRING = "host='localhost' dbname='test' user='postgres' password='pwd'"
# views.py
from constants import CONN_STRING
def location_where_connection_variables_needed(request, some_id):
conn = psycopg2.connect(CONN_STRING)
cursor = conn.cursor()
Also, make sure to clear out any .pyc files that might not be getting cleared out by manage.py. That can sometimes cause things not to work as expected. You might also stay away from import * since it's not as implicit (you can't see what you're importing) and it's easy to run into naming collisions.
You can try a global variable instead of a local one. Use it as
global conn_string = "host='localhost' dbname='test' user='postgres' password='pwd'"

Separate custom settings variable between development, staging and production

I'm following the project structure as laid out by Zachary Voase, but I'm struggling with one specific issue.
I'd very much like to have a custom settings boolean variable (let's call it SEND_LIVE_MAIL) that I would be using in the project. Basically, I'd like to use this settings variable in my code and if SEND_LIVE_MAIL is True actually send out a mail, whereas when it is set to False just print its contents out to the console. The latter would apply to the dev environment and when running unittests.
What would be a good way of implementing this? Currently, depending on the environment, the django server uses dev, staging or prd settings, but for custom settings variables I believe these need to be imported 'literally'. In other words, I'd be using in my views something like
from settings.development import SEND_LIVE_MAIL
which of course isn't what I want. I'd like to be able to do something like:
from settings import SEND_LIVE_MAIL
and depending on the environment, the correct value is assigned to the SEND_LIVE_MAIL variable.
Thanks in advance!
You shouldn't be importing directly from your settings files anyways. Use:
>>> from django.conf import settings
>>> settings.SEND_LIVE_MAIL
True
The simplest solution is to have this at the bottom of your settings file:
try:
from local_settings import *
except ImportError:
pass
And in local_settings.py specify all your environment-specific overrides. I generally don't commit this file to version control.
There are more advanced ways of doing it, where you end up with a default settings file and a per-environment override.
This article by David Cramer covers the various approaches, including both of the ones I've mentioned: http://justcramer.com/2011/01/13/settings-in-django/
import os
PROJECT_PATH = os.path.dirname(__file__)
try:
execfile(os.path.join(PROJECT_PATH, local_settings.py'))
except IOError:
pass
Then you can have your local_settings.py behave as if it was pasted directly into your settings.py:
$ cat local_settings.py
INSTALLED_APPS += ['foo']
You can do something like this for a wide variety of environment based settings, but here's an example for just SEND_LIVE_MAIL.
settings_config.py
import re
import socket
class Config:
def __init__(self):
fqdn = socket.getfqdn()
env = re.search(r'(devhost|stagehost|prodhost)', fqdn)
env = env and env.group(1)
env = env or 'devhost'
if env == 'devhost':
self.SEND_LIVE_MAIL = # whatever
elif env == 'stagehost':
self.SEND_LIVE_MAIL = # whatever
elif env == 'prodhost':
self.SEND_LIVE_MAIL = # whatever
config = Config()
settings.py
from settings_config import config
SEND_LIVE_MAIL = config.SEND_LIVE_MAIL

Django makemessages ignore switch doesn't work for me

I have problems localizing a django-nonrel project, which is deployed to GAE. Because of GAE I have to put everything into my project folder, so it looks like something like this
project
+ django
+ dbindexer
+ registration
+ myapp
...
+ locale
+ templates
I have strings to localize in templates directory, and in the myapp directory.
When I run python manage.py makemessages -l en --ignore django\* from the project dir it crawl through all the directories of the project, including django, so I get a quite big po file. My strings from the templates are there, along with all of the strings from django directory.
after --ignore ( or just -i ) I tried to pu django django/* , but nothing changed.
Any ideas?
./manage.py help makemessages
-i PATTERN, --ignore=PATTERN
Ignore files or directories matching this glob-style
pattern. Use multiple times to ignore more.
I have just tested it, and this command successfully ignored my application:
./manage.py makemessages -l da -i "django*"
But beware that before you test it, you should delete the old .po file, as I think it will not automatically remove the translation lines from your previous makemessages execution.
The problem is with the pattern - maybe the shell was expanding it for you.
In general - it is good to avoid path separators (whether / or \) in the pattern.
If you need to always pass specific options to the makemessages command, you could consider your own wrapper, like this one, which I use myself:
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.management import call_command
class Command(BaseCommand):
help = "Scan i18n messages without going into externals."
def handle(self, *args, **options):
call_command('makemessages',
all=True,
extensions=['html', 'inc'],
ignore_patterns=['externals*'])
This saves you typing, and gives a common entry point for scanning messages across the project (your translator colleague will not destroy translations by missing out some parameter).
Don't delete the old .po file, once you have cleared it from the totally unwanted (i.e. - those from 'django' directory) messages. This allows gettext to recycle old unused messages, once they are used again (or simmilar ones, which will be marked as #, fuzzy.
Edit - as mt4x noted - the wrapper above doesn't allow for passing the options to the wrapped command. This is easy to fix:
from django.core.management import call_command
from django.core.management.commands.makemessages import (
Command as MakeMessagesCommand
)
class Command(MakeMessagesCommand):
help = "Scan i18n messages without going into externals."
def handle(self, *args, **options):
options['all'] = True
options['extensions'] = ['html', 'inc']
if 'ignore_patterns' not in options:
options['ignore_patterns'] = []
options['ignore_patterns'] += ['externals*']
call_command('makemessages', **options)
Thus - you can fix what needs to be fixed, and flex the rest.
And this needs not be blind override like above, but also some conditional edit of the parameters passed to the command - appending something to a list or only adding it when it's missing.