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

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

Related

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

Read static file in view

To integrate Django and Ember, I have decided to serve my Ember SPA in a Django view (avoids CORS issues, only one server for frontend and API, etc). I do it like this:
# urls.py
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^api/', include(api_urls, namespace='api')),
...
url(r'^$', views.emberapp, name='emberapp'),
...
]
# views.py
from django.http import HttpResponse
def emberapp(request):
# The Ember frontend SPA index file
# This only works in development, and is anyway hacky
EMBER_FE_INDEX_HTML = '/absolute/path/to/my/frontend/static/fe-dist/index.html'
template_file = open(EMBER_FE_INDEX_HTML)
html_content = index_file.read()
index_file.close()
return HttpResponse(html_content)
The index.html is part of the static assets. In development this is very easy:
The index.html is directly accessible to the Django application in the file system
I know the absolute path to the index file
But in production things are more complex, because the static assets are not local to the django application, but accessible on Amazon S3. I use django-storages for that.
How can I read the contents of a static file from a view, in a generic way, no matter what backend is used to store/serve the static files?
First, I don't think the way you do it is a good idea.
But, to answer your question: In your settings.py, you likely have defined the directory where Django will collect all static files.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
So in your view, you just need to fetch the file os.path.join(settings.STATIC_ROOT, 'index.html')
That said, you should serve index.html via the webserver, same as your static/ files, robots.txt, favicon.ico, etc. Not through Django. The webserver is much faster, uses proper caching, and its just one line in your Nginx or Apache settings, instead of an entire view function in Django.
This is my current solution. Works in development, not sure about production yet (it is a pain that you need to commit untested code to verify production-related code in Heroku)
from django.conf import settings
from django.http import HttpResponse
from django.core.files.storage import get_storage_class
FE_INDEX_HTML = 'fe/index.html' # relative to the collectstatic directory
def emberapp(request):
# The Ember frontend SPA index file
# By getting the storage_class like this, we guarantee that this will work
# no matter what backend is used for serving static files
# Which means, this will work both in development and production
# Make sure to run collectstatic (even in development)
# TODO: how to use this in development without being forced to run collectstatic?
storage_class = get_storage_class(settings.STATICFILES_STORAGE)
# TODO: reading from a storage backend can be slow if assets are in a third-party server (like Amazon S3)
# Maybe streaming the static file from the server would be faster?
# No redirect to the Amazon S3 asset, please, since the Ember App needs to
# run from the same URL as the API, otherwise you get CORS issues
with storage_class().open(FE_VWORKS_INDEX_HTML) as index_file:
html_content = index_file.read()
return HttpResponse(html_content)
Or, to reply with an StreamingHttpResponse, which does not force Django to read the whole file in memory (and wait for it to be read):
def emberapp(request):
# The Ember frontend SPA index file
# By getting the storage_class like this, we guarantee that this will work
# no matter what backend is used for serving static files
# Which means, this will work both in development and production
# Make sure to run collectstatic (even in development)
# TODO: how to use this in development without being forced to run collectstatic?
storage_class = get_storage_class(settings.STATICFILES_STORAGE)
index_file = storage_class().open(FE_INDEX_HTML)
return StreamingHttpResponse(index_file)

How to get the link of the file in a FileField?

how can I get the link of a FileField? I tried the url field, but it gives the file path:
In [7]: myobject.myfilefield.path
Out[7]: u'/home/xxxx/media/files/reference/1342188147.zip'
In [8]: myobject.myfilefield.url
Out[8]: u'/home/xxxx/media/files/reference/1342188147.zip'
I was expecting to get http://<mydomain>/media/files/reference/1342188147.zip
How can I get that? Do I have to build the string manually?
EDIT
My MEDIA_URL was not set, but I still can't get it to work:
settings.py
MEDIA_ROOT = '/home/xxx/media/'
MEDIA_URL = 'http://xxx.com/media/'
models.py
class Archive(models.Model):
#...
file = models.FileField(upload_to="files")
in shell
a = Archive()
a.file = "/some/path/to/file.txt"
a.save()
Then I get for a.path:
"/some/path/to/file.txt"
and for a.url:
"http://xxx.com/media/some/path/to/file.txt"
When done programmatically, a.file = "/some/path/to/file.txt", the file is not uploaded to MEDIA_ROOT. How can I upload a file in the directory defined by upload_to, i.e. in my case /home/xxx/media/file.txt?
The output is based on your settings file, have a look here for an understanding on serving staticfiles in development and/or production:
Confusion in Django admin, static and media files
I'm guessing you have the field defined as:
picture = models.ImageField(upload_to="/home/xxx/media/files/reference")
in other words is it possible you have defined an absolute path for the upload_path property of the field ?
Try something like
from django.conf import settings
picture = models.ImageField(upload_to=settings.MEDIA_ROOT + "files/reference")

Override default Django translations

I have a template with this:
{% trans "Log out" %}
This is translated automatically by Django to Spanish as Terminar sesión. However I would like to translate it as Cerrar sesión.
I have tried to add this literal to the .po file, however I get an error saying this literal is duplicated when I compile the messages.
Is there a way to change/override default Django translations?
Thanks.
This is what worked for me:
create a file in your app folder which will hold django messages for which translations need to be overridden, e.g. django_standard_messages.py
in django lib folder or in django.po files find the message (string) that needs to be overridden, e.g. django.forms/fields.py has message _(u"This field is required.") which we want to translate to german differently
in django_standard_messages.py add all such messages like this:
# coding: utf-8
_ = lambda s: s
django_standard_messages_to_override = [
_("This field is required."),
...
]
Translate the file (makemessages, compilemessages) - makemessages will add added django standard messages in your application .po file, find them and translate, run compilemessages to update .mo file
tryout
The logic behind: (I think ;) ) - when ugettext function searches translation for one message (string), there are several .po/.mo files that needs to be searched through. The first match is used. So, if our local app .po/.mo is first in that order, our translations will override all other (e.g. django default).
Alternative
When you need to translate all or most of django default messages, the other possibility (which I didn't tried) is to copy default django .po file in our locale or some other special folder, and fix translations and register the folder (if new) in LOCALE_PATHS django settings file as first entry in the list.
The logic behind: is the very similar as noted in previous section.
Based on Robert Lujo answer, his alternative is totally working. And IMO simpler (keep the overriden locales in a special .po file only). Here are the steps:
Add an extra path to the LOCALE_PATHS Django settings.
LOCALE_PATHS = (
# the default one, where the makemessages command will generate the files
os.path.join(BASE_DIR, 'myproject', 'locale'),
# our new, extended locale dir
os.path.join(BASE_DIR, 'myproject', 'locale_extra'),
)
find the original Django (or 3rd party) string to be translated
ex.: "recent actions" for the Django admin 'recent actions' block
Add the new .po file "myproject/locale_extra/en/LC_MESSAGES/django.po" with the alternative translation :
msgid "Recent actions"
msgstr "Last actions"
Compile your messages as usual
The easiest way is to collect the .po file found in the django.contrib.admin locale folder and re-compiling it (you can use POEdit for doing so).
You could also override the django.contrib.admin templates by putting them in your projects templates folder (for example: yourproject/templates/admin/change_form.html) then running makemessages from the project root (although this is no longer supported for django 1.4 alpha if i'm correct)
edit: Robert Lujo's answer is the clean method
This is another solution we deployed. It involved monkey patching the _add_installed_apps_translations method of the DjangoTranslation class to prioritize the translations of the project apps over the translations of the Django apps.
# patches.py
from __future__ import absolute_import, unicode_literals
import os
from django.apps import apps
from django.core.exceptions import AppRegistryNotReady
from django.utils.translation.trans_real import DjangoTranslation
def patchDjangoTranslation():
"""
Patch Django to prioritize the project's app translations over
its own. Fixes GitLab issue #734 for Django 1.11.
Might needs to be updated for future Django versions.
"""
def _add_installed_apps_translations_new(self):
"""Merges translations from each installed app."""
try:
# Django apps
app_configs = [
app for app in apps.get_app_configs() if app.name.startswith('django.')
]
# Non Django apps
app_configs = [
app for app in apps.get_app_configs() if not app.name.startswith('django.')
]
app_configs = reversed(app_configs)
except AppRegistryNotReady:
raise AppRegistryNotReady(
"The translation infrastructure cannot be initialized before the "
"apps registry is ready. Check that you don't make non-lazy "
"gettext calls at import time.")
for app_config in app_configs:
localedir = os.path.join(app_config.path, 'locale')
if os.path.exists(localedir):
translation = self._new_gnu_trans(localedir)
self.merge(translation)
DjangoTranslation._add_installed_apps_translations = _add_installed_apps_translations_new
Then in the .ready() method of your main app, call patchDjangoTranslation:
from .patches import patchDjangoTranslation
class CommonApp(MayanAppConfig):
app_namespace = 'common'
app_url = ''
has_rest_api = True
has_tests = True
name = 'mayan.apps.common'
verbose_name = _('Common')
def ready(self):
super(CommonApp, self).ready()
patchDjangoTranslation() # Apply patch
The main change are these lines:
# Django apps
app_configs = [
app for app in apps.get_app_configs() if app.name.startswith('django.')
]
# Non Django apps
app_configs = [
app for app in apps.get_app_configs() if not app.name.startswith('django.')
]
app_configs = reversed(app_configs)
The original are:
app_configs = reversed(list(apps.get_app_configs()))
Instead of interpreting the translations of the apps in the order they appear in the INSTALLED_APPS setting, this block outputs the list of apps placing the project apps before the Django apps. Since this only happens when determining the translation to use, it doesn't affect any other part of the code and no other changes are necessary.
It works on Django version 1.11 up to 2.2.

Problem loading custom template tags (Error: No module named x)

I am currently writing a few custom template tags but for some reason they will not load. My directory structure is as follows:
MyProj
|
----MyApp
|
|----templatetags
|
|----myapp_tags.py
|----__init__.py
In myapp_tags.py
from django.template import Library, Node
from myproj.myapp.models import Product
register = Library()
class LatestProductsNode(Node):
def render(self, context):
context['recent_products'] = Product.objects.all()[:5]
return ''
def get_latest_products(parser, token):
return LatestProductsNode()
get_latest_products = register.tag(get_latest_products)
In settings.py
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'myproj.myapp',
)
In the Template
{% load myapp_tags %}
The error i get when trying to load the page:
Exception Type: TemplateSyntaxError Exception Value:
'myapp_tags' is not a valid tag library: Could not load template library from django.templatetags.myapp_tags, No module named myapp_tags
in settings.py, you should never name the project 'myproj' explicitely. In INSTALLED_APPS, just use 'myapp'. Also, you should have this :
TEMPLATE_LOADERS = (
'django.template.loaders.app_directories.load_template_source',
)
And be sure to have an __init__.py in the myapp folder as well as in templatetags.
Use manage.py shell then from myapp.templatetags import myapp_tags to find out if theres any python error in the myapp_tags.py file.
Also, be sure that myapp_tags.py file name doesnt conflicts with another folder/file in your project.
Hope this helps.
One thing that's tripped me up is that the magic importing of templatetags bypasses the automatic reloading of the development server.
If the following works in manage.py shell
>>> from django.templatetags import myapp_tags
>>>
Then everything is actually working and you just need to reload the development server. If on the other hand you get an ImportError then something is wrong and you should check your INSTALLED_APPS, that you have an __init__.py file in the templatetags directory and all the other things suggested in the other answers.
This will probably only apply to a tiny fraction of the people who experience template tag loading problems, but this is the second time I've arrived at this question in as many weeks and both times it's just taken restarting the development server to get things working.
Some reasons:
due to error in templatetgs code.
If you have used model import in templatetags
For #2, for example. If you are doing:
from your_app2.models import model
This will go wrong, so instead above, you should do
from your_project.your_app2.models import model
It worked me this way.
I just came across the same problem in Django 2 and realized that the custom template tag files must have unique names across all of your project apps.
The problem is that nyapp_tags is not at the top level of an installed project. If you put myproj.myapp.templatetags in INSTALLED_APPS, you should be fine.