Recursive app dependencies django - django

Django's INSTALLED_APPS is a list in the settings file, which ultimately the devops girl/guy of the wsgi-application is responsible for.
However, when creating an app, I often use templates and templatetags of other apps, for example django-bootstrap4. Consider a simple bootstrap4-with-butter app, that only proivides this template:
bs4wb/templates/bs4wb/base.html
{% extends "bootstrap4/bootstrap4.html" %}
{% block bootstrap4_extra_head %}
<script> confirm('with butter?!'); </script>
{% endblock bootstrap4_extra_head %}
It is not enough for the devops to install and add to INSTALLED_APPS
bs4wb, they also need to do the same for django-bootstrap4, and moreover, he/she also needs to keep track of the version I used whenever I upgrade from django-bootstrap4 to django-bootstrap5` or something. That is, I need to document an extra step which can change.
How can I specify recursive INSTALLED_APPS? Is there something like an app-union? For example (obviously ugly syntax, sorry):
export('bs4wb', app_by_union(['bootstrap4', 'bs4wb']) )
which would insert bootstrap4 and bs4wb next to each other whenever bs4wb is added to INSTALLED_APPS?
Or should I just raise an exception if bootstrap4 was not added to installed apps, stating that it should be added.

As an app developer you usually use django's system check framework to ensure the environment fits your requirements.
In your case you'll check whether another app is included in INSTALLED_APPS and add an error, if not.
It might look like that (untested):
from django.conf import settings
from django.core.checks import Error, register
#register()
def bootstrap4_check(app_configs, **kwargs):
errors = []
if not 'django-bootstrap4' in settings.INSTALLED_APPS:
errors.append(
Error('django-bootstrap4 needs to be in INSTALLED_APPS')
)
return errors
If your requirements change with new versions: adjust the checks.

Related

Django templates url - differentiate between same url names

in my application I've got a view called download_document which works fine using {% url 'download_document' some_id %} .
Installing a 3rd party app into the virtual environment cause trouble because this app has also the download_document view (also accepting one ID parameter) with the same urlname.
If I'd like to use both with this name, can I somehow make the difference between them? For instance using both cases in the same template file?
Python: 2.7
Django: 1.11.16
Debian .
Update #1:
my app is called infk . I tried already using
{% url 'infk:download_document' document.id %}
, but in this case I got a 500 with: u'infk' is not a registered namespace.
I also tried adding namespace to the top urls.py file:
#original
url(r'^infk/', include('infk.urls')),
#modified
url(r'^infk/', include('infk.urls', namespace="infk")),
but in this case i got back a different 500: Reverse for 'infk' not found. 'infk' is not a valid view function or pattern name.
To work the second one I'd need to change at least all reverse methods from ** reverse('infk'...) to reverse('infk:infk'...) which I don't really want.
So if I don't add namespace, is there any way to call my download_document function? (By default it calls the 3rd party app's download_document view .
Long post ahead, so deep breath...
So if I don't add namespace, is there any way to call my download_document function? (By default it calls the 3rd party app's download_document view .
That depends on what you're trying to do. If you always want your download_document function to be called throughout your entire website (even on the pages provided by the 3rd-party-app), you can just move infk to the end of your urlpatterns in main_app/urls. That would look something like this:
main_app/urls.py
================================================================================
from django.conf.urls import include, url
urlpatterns = [
url(r'^3rd-party-app/', include('3rd-party-app.urls')),
url(r'^infk/', include('infk.urls')),
]
Otherwise, if you're trying to get your download_document in specific instances without namespaces, it's not particularly easily. You could create a custom tag that always produces the url for your download_document function, but that's a lot of extra work, and if you run into another name conflict, you'd have to do it again and again, and that's barely a step above hard-coding the url in the first place. Plus, if you're planning to deploy infk as a third party app, you'll probably want to use a namespace to help other people avoid the same problem you're having.
Explanation
According to the docs at https://docs.djangoproject.com/en/1.11/topics/http/urls/#naming-url-patterns:
If you call your URL pattern comment and another application does the same thing, the URL that reverse() finds depends on whichever pattern is last in your project’s urlpatterns list.
So if you change the order of your included urls in your main_app/urls.py file such that infk is after the 3rd-party app, you should get the infk download_document. However, this means that you'll ALWAYS get the infk download_document, which may break some of the functionality in your 3rd-party app.
Examples
Django 1.x
Source: https://docs.djangoproject.com/en/1.11/topics/http/urls/#reversing-namespaced-urls
Your modified url(r'^infk/', include('infk.urls', namespace="infk")) has created an instance namespace, which might make sense if you are using multiple instances of infk throughout your app. If you aren't (or you're planning on deploying infk as a third-party app itself) you probably don't need that. Either way, I'd recommend you set the application namespace.
main_app/urls.py
================================================================================
from django.conf.urls import include, url
urlpatterns = [
# These are both instance namespaces. Note that although the same urls
# are being included from infk.urls for both, the final route will be
# different.
url(r'^infk-instance1/', include('infk.urls', namespace='infk-instance1')),
url(r'^infk-instance2/', include('infk.urls', namespace='infk-instance2')),
# The 3rd-party app your url and reverse functions are colliding with.
url(r'^3rd-party-app/', include('3rd-party-app.urls')),
]
infk/urls.py
================================================================================
from django.conf.urls import url
from . import views
# This is the application namespace. I recommend setting this regardless of
# whether you use instance namespaces.
app_name = 'infk'
urlpatterns = [
url(r'^(?P<pk>\d+)/$', views.download_document(), name='download_document'),
]
Instance Namespaces
Instance namespaces would be called like this:
infk/foo_template.html
================================================================================
...
<!-- In this case, {% url 'infk-instance1:download_document' document.id %}
will return /infk-instance1/<document.id>-->
Download Document
...
ingk/foo_template.html
================================================================================
...
<!-- In this case, {% url 'infk-instance2:download_document' document.id %}
will return /infk-instance2/<document.id>-->
Download Document
...
You should still be able to get the 3rd-party download_document url, if you need it. Note that in order for this to work, 3rd-party-app.urls must be included AFTER infk.urls.
ingk/foo_template.html
================================================================================
...
<!-- In this case, {% url 'download_document' document.id %}
will return /3rd-party-app/<document.id>-->
Download Document
...
Application Namespaces
The behavior of application namespaces depends on whether there are instance namespaces defined for the application or not, and whether the view called defines a current_app. If you use the above example where both application and instance namespaces are defined, then using an application namespace in your {% url %} tag may return inconsistent results. (See https://docs.djangoproject.com/en/1.11/topics/http/urls/#reversing-namespaced-urls for more information)
For ease of explanation, I'm going to assume you are not using multiple instances of the infk app. Your main_app/urls.py might then look like:
main_app/urls.py
================================================================================
from django.conf.urls import include, url
urlpatterns = [
# No instance namespaces
url(r'^infk/', include('infk.urls')),
url(r'^3rd-party-app/', include('3rd-party-app.urls')),
]
Application namespaces are called like this:
infk/foo_template.html
================================================================================
...
<!-- In this case, {% url 'infk:download_document' document.id %}
will return /infk/<document.id>-->
Download Document
...
And, assuming that 3rd-party app is installed AFTER infk, your 3rd-party app's download_document can still be called like:
ingk/foo_template.html
================================================================================
...
<!-- In this case, {% url 'download_document' document.id %}
will return /3rd-party-app/<document.id>-->
Download Document
...
Django >=2.x
Source: https://docs.djangoproject.com/en/dev/topics/http/urls/#term-application-namespace
The resolver works essentially the same as described above; however, instead of using url() in your urls.py files, use path() or re_path().
There is a way to avoid url name conflicts. All you have to do is to add their respective namespaces before the url name. As per the docs, all you need is the following:
{% url 'myapp:view-name' %}
By using the namespaced URL resolution strategy.

gae_mini_profiler {% profiler_includes %} gives Invalid block tag: 'profiler_includes'

I am attempting to install gae_mini_profiler in my django-nonrel app
I placed the {% profiler_includes %} tag at the bottom of my base.html
It results in a
Exception Type: TemplateSyntaxError
Exception Value: Invalid block tag: 'profiler_includes'
I placed
from gae_mini_profiler import profiler
application = profiler.ProfilerWSGIMiddleware(application)
at the bottom of djangoppengine/main/__init__.py
I followed all the other instructions at https://github.com/kamens/gae_mini_profiler#start
What am I doing wrong?
I solved this by changing gae_mini_profiler/templatetags.py to be a true template tag library.
To do this create a package called templatetags, and then move(and rename) the templatetags.py module to profiler_tags.py.
Inside of profiler_tags.py make the following changes:
Change:
from google.appengine.ext import webapp
register = webapp.template.create_template_register()
To:
from django.template import Library
register = Library()
Change:
path = os.path.join(os.path.dirname(__file__), "templates/includes.html")
To:
path = os.path.join(os.path.dirname(__file__), "../templates/includes.html")
In your settings file add gae_mini_profiler to your list of installed apps.
Remove all references to
template.register_template_library('gae_mini_profiler.templatetags')
In your templates whereever you had {% profiler_includes %} you then need to add a load block
{% load profiler_tags %}
I think that is all of the changes, but need to go check my git log.
Are you using the new Python 2.7 runtime for GAE? If so, the django template setup is slightly different and gae_mini_profiler hasn't been upgraded yet (anyone is welcome to submit this fix, haven't gotten to it yet).
It should be easy to work around as the only thing you need to do is find a way to render the HTML string returned by gae_mini_profiler.templatetags.profiler_includes() anywhere in your page. There are a number of methods of accomplishing this if the built-in template tag isn't working as-is. You could simply call the function in your base request handler and pass the resulting html into your base template if absolutely necessary (although this is admittedly a gross hack).
We'll hopefully have Python 2.7 working w/ gae_mini_profiler shortly. If you're not on Python 2.7, I'm not sure what the issue is as I'd expect the current code to work...

Django - Pre render signal?

I have a 'live settings' app which I use to store certain global site settings. Certain pages of my site reference these settings, although generally they only use 2-3 at a time. I access these settings using a custom template tag like so:
{% settings site_name %}
Each time I use this tag, the model will retrieve the setting from the database, so if I had something like:
{% settings site_name %} {% settings num_articles %}
the database would get queried twice.
What I'd like to do is not hit the database more than once per page for the settings values, if I could get all the settings used on a page and then hit the database to retrieve them all, that would be much better.
I had a look through the documentation and it seems Django has no pre render signal which I could use to update any references to the settings, so does anyone have any suggestions how I could optimise this system? And yes obviously caching is the obvious answer but I'd still like to improve on this for when the page isn't cached.
Agreeing with Daniel that process_template_response is the hook you're looking for. I don't know how many settings you have, but it could also make sense to retrieve all settings once, and store them in some dictionary like structure, so you can access them anytime without hitting the database. You could retrieve them once either per request, or - even if your settings do barely change - when django initializes. You could additionally use django's signals to update your cached settings on delete or update.
If you have a look at django-dbsettings you will see it does a similar thing!
Django 1.3 includes the new TemplateResponse class, which allows you to modify the response later in the rendering process - eg in middleware. This looks like it might do what you want.
If you wish to stick to a scheme similar to what you already have, I'd implement a filter that takes in a list of keys and returns a dict with all the relevant values.
Example filter:
def get_settings(key_list):
# take values from conf.settings. In practice, can be from any source
return dict((k, getattr(settings, k, None)) for k in key_list.split(","))
register.filter("get_settings", get_settings)
Usage:
{# load multiple values with a single call #}
{% with "KEY1,KEY2"|get_settings as settings %}
setting values {{ settings.KEY1 }} and {{ settings.KEY2 }}
{% endwith %}
Note that I used a filter instead of a tag because the with builtin expects a single value before as.

Difficulty overriding Django Admin template

I'm using Django 1.2.4 on Ubuntu 10.10. I'm trying to override the index.html template for the admin module. I've been following these instructions. I also looked at this question, but I'm still having difficulty.
The instructions say to create an admin directory in the templates directory:
templates/
admin/
index.html
I want to override a single block in the index.html. (Really, all I want to do is append some text to the end. Is there an easier way than copy/pasting the entire block and changing it?) (Update: Looks like {{block.super}} may help.)
To signal that I'm overriding, I put this at the top of my index.html:
{% extends "admin/index.html" %}
Of course, that results in a stack overflow (from the terminal):
Exception RuntimeError: 'maximum recursion depth exceeded in __subclasscheck__' in <type 'exceptions.RuntimeError'> ignored
What is the correct way to do this? I tried a symlink per an answer on the linked question, but that resulted in the following:
me#mycomp:~/foo$ sudo ln -s /usr/local/lib/python2.6/dist-packages/django/contrib/admin/templates/ django_admin
[sudo] password for me:
ln: creating symbolic link `django_admin': Protocol error
What am I doing wrong?
The recursion error is because you're extending the admin/index.html with itself.
You can either:
copy the entire admin/index.html template in your templates/admin/ directory, and it will replace the default template with yours
override the index.html per app or model, as explained here
I know this is late after the question, but you know, google traveling...
Amend settings.py with an extra template folder, for example:
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
"/home/mysite/webapps/django/myproject/templates",
"/home/mysite/webapps/django/lib/python2.7/django/", # extra folder
)
Then in myproject/templates/admin add your own index.html like:
{% extends "contrib/admin/templates/admin/index.html" %}
{% block branding %}
<h1 id="site-name">Administration for TheLittleButtonCo</h1>
{% endblock %}
Variations are possible, obviously. This works on Django 1.3.1 final
Not sure if you found the answer, but you need to change
{% extends "admin/index.html" %}
to
{% extends "admin/base_site.html" %}
as that is what the original index.html page overwrites. Because the Django system searches your templates folder before using the default admin one, so in this case it finds the admin/index.html in your templates, then it's trying to extend itself with the extend (hence the recursion error).
For reference you can customise the base_site.html in you templates too, it extends base.html. The best thing to do is copy the original from:
/usr/local/lib/python2.6/dist-packages/django/contrib/admin/templates/
and paste it into your templates folder as a starting point
I use an extra package, called django-smart-extends

Django: Using Different Templates for Production

In my base.html template which is imported in every Django template, I have a block with the Google Analytics script:
<!-- GOOGLE ANALYTICS -->
<script type="text/javascript">
bla bla...
</script>
I would like to have this script only included when in production, but not during development. Is there a solution for this comparable to the solution in setting.py?
import socket
if socket.gethostname() == 'my-laptop':
DEBUG = TEMPLATE_DEBUG = True
else:
DEBUG = TEMPLATE_DEBUG = False
Anybody who knows of a template tag, or should I do my own one?
You could add your DEBUG variable to a context processor and just put an IF around that block.
http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext
from django.conf import settings
def debug_context(request):
return {'DEBUG': settings.DEBUG}
Then in your template:
{% if DEBUG %}
STUFF
{% endif %}
Alternatively you could make the context processor return anything you want to key off of, anything in your settings file or otherwise.
In settings.py,Check debug is True, also add:
INTERNAL_IPS = (
'127.0.0.1',
'localhost',
)
Then you can use things in your template like this:
{% if debug %}
<span>This is debug</span>
{% else %}
<span>This is production</span>
{% endif %}
If want to change to production, set debug to False.
Ref:
http://www.djangobook.com/en/2.0/chapter09.html
Haven't seen such a template tag yet.
I'm more inclined to use different setting.py files for production and dev, and include analytics code as described in this answer.
This solution allows for a simpler template, and gives you the option to use different analytics code in different deployments of the same app.
The template is probably not the best place to handle this.
usually, you'd write a context preprocessor that will process the request and add a flag to your context (such as PRODUCTION = True). Then you can use a simple if statement in the template, or better yet, write a simple custom tag that will hide that implementation detail from the template.
See also this answer.
I do the following on lots of sites and it seems to work really well.
Then in my static media file directory I have a copy of base.css and base_dev.css. I do all my development in the base_dev.css file then merge it into base.css before I launch.