Adapt a view if an app is installed with Django - django

I have a web app with a project that works alone (it's index, login.. pages).
I would need to change the index page if a new app is installed (e.g: add a link, a table in the template with my app models..). Have it dynamic.
The removal of the app must let the project intact and just remove the link.
How can I do that? Is it possible?

You can use the Django's application registry:
In [1]: from django.apps import apps
In [2]: apps.is_installed("django.contrib.admin")
Out[2]: True
An application can actually be enabled by using a dotted Python path to either its package or the application's configuration class (preferred). Simply checking if "app_name" is in settings.INSTALLED_APPS will fail in the latter case.

def my_view(request):
from django.conf import settings
app_installed = 'app_name' in settings.INSTALLED_APPS
return render_to_response(template_name, {'app_installed': app_installed})
template:
{% if app_installed %}
...
{% endif %}

Or use a custom context processor.
In installed_apps.py
from django.conf import settings
def installed_apps(request):
return {
'app_installed' : 'app_name' in settings.INSTALLED_APPS
}
In settings.py:
TEMPLATE_CONTEXT_PROCESSORS = (
...
'installed_apps.installed_apps'
)

simple_tag version:
The tricky part is that you can't pass arguments to your simple_tag when it's used in a conditional. Therefor, you create a new variable specifically for your installed app with as is_myapp_installed.
In templatetags/my_filters.py:
from django import template
register = template.Library()
#register.simple_tag
def is_app_installed(app):
from django.apps import apps
return apps.is_installed(app)
In template:
{% load my_filters %}
...
{% is_app_installed "myapp" as is_myapp_installed %}
{% if is_myapp_installed %}
...
{% endif %}

Related

ValueError: server must be a Flask app or a boolean

I'm trying to work through the simplest dashboard example in the django-plotly-dash documentation, but I'm consistently getting the ValueError above.
For the code below, assume the django project name is django_project and the django app name is dashboard.
My ROOT_URLCONF at django_project/urls.py has the following relevant code:
import dashboard.dash_app
from dashboard.views import test_view
urlpatterns = [
...
path('dashboard/', test_view, name='test_view'),
path('django_plotly_dash/', include('django_plotly_dash.urls')),
]
My dashboard app view, located at dashboard/views.py is as follows:
from django.shortcuts import render
def test_view(request):
return render(request, 'dashboard/main.html')
The main.html template is as follows:
from django.shortcuts import render
def test_view(request):
return render(request, 'dashboard/main.html')
{% load plotly_dash %}
{% plotly_app name="SimpleExample" %}
Finally, the DjangoDash app instance is created in a file called dashboard/dash_app.py. As shown earlier, this module is imported in django_project/urls.py, as above. Code is as follows:
import dash
import dash_core_components as dcc
import dash_html_components as html
from django_plotly_dash import DjangoDash
app = DjangoDash('SimpleExample')
app.layout = ...
#app.callback(...)
def callback_color(...):
...
During the debugging process, the only other seemingly relevant information that I have is that the base_pathname is '/django_plotly_dash/app/SimpleExample/'
Any other ideas?
This is caused by the recent update to Dash version 1.0 on 2019-06-20. The Dash class now checks to ensure that server is either boolean or an instance of Flask. Since django plotly_dash uses it's own PseudoFlask object, it fails this check and it is incompatible with Dash 1.0 and will need to be updated for use with the current Dash version.
I experienced the same problem today. Using an older version of Dash (0.43.0) solved it for me.

Django & Jinja2 templates using {{ url() }}

I am trying to figure out how to pass my user_id within my html using jinja's {{ url() }}, using urls that don't need any id like /dashboard/ work fine but I need to pass an id to this- example: /user/3 . I have tried the following with no success:
{{ url('detail') }}
{{ url('detail', user_id=User.id) }}
{{ url('detail', User.id) }}
Here's part of my views and html:
views.py
urlpatterns = [
path('dashboard/', dashboard, name='dashboard'),
path('user/<int:user_id>/', detail, name='detail'),
]
dashboard.html
{% for User in all_users %}
{{ url('detail') }}
{% endfor %}
Any help on this would be appreciated, thanks
I found a solution:
{% for User in all_users %}
{{ url('detail', args=[User.id] )}}
{% endfor %}
This also works:
{% url 'detail' user.id %}
Then, in views.py, your corresponding function should receive user_id as an argument (internally user.id becomes user_id).
Django now has official backend support for Jinja2 - I'm guessing you were using django-jinja which provides an implementation of the url function but it's fairly straightforward to integrate Django and Jinja now without adding any additional dependencies (besides Jinja2).
First, configure your TEMPLATES in your settings file and add a dictionary entry with the jinja2 backend:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'APP_DIRS': True,
'OPTIONS': {
'extensions': [<custom extensions if any>]
'environment': 'yourapp.jinja2.environment',
...
},
},
...
]
The key part of this is the environment setting. The examples suggest creating a jinja2.py file in your app directory and defining an environment function there that you'll set to a function that returns a Jinja2 Environment, e.g.,
# example yourapp/jinja2.py
from django.conf import settings
from django.urls import reverse
from django.templatetags.static import static
from jinja2 import Environment
def environment(**options):
env = Environment(**options)
env.globals.update({
'static': static,
'url': reverse,
'settings': settings,
...
})
return env
Here we are binding Python functions (and Django things) to be made available in our Jinja2 environment, so now static in a jinja template can be called static(...) and invoke the Django static function defined in django.templatetags.static, and url is bound to the Django reverse function so url('detail', args=[user.id]) should work. If you prefer a signature like {{ url('detail', pk=123) }} without generating an invalid arguments error you can define your own function:
def jinja_url(viewname, *args, **kwargs):
return reverse(viewname, args=args, kwargs=kwargs)
and bind 'url': jinja_url in your TEMPLATES settings instead.
It's also important to note that Django looks for your jinja templates in a jinja2 directory under your app dir, not the templates directory.

Django display list items based on URL

I'm trying to hide/show sections of my navigation depending on my active URL.
I have attempted to do this using the re.match() method, but jinja did not like this. This code is in an HTML include file for my side navigation and is as follows:
<ul>
{% if bool(re.match('^/url/path', request.get_full_path)) %}
<li><a href='link1'>Link1</a></li>
<li><a href='link1'>Link2</a></li>
<li><a href='link1'>Link3</a></li>
{% endif %}
</ul>
Thanks in advance.
You can create a custom filter and use it. Something like this maybe;
# nav_active.py
import re
from django.template import Library
from django.core.urlresolvers import reverse
register = Library()
#register.filter()
def nav_active(request_path, search_path):
# WRITE YOUR LOGIC
return search_path in request_path
Inside the template
{% load nav_active %}
{% if request_path|nav_active:"/search/path" %}
....
{% endif %}
Update as per your comment. From Django docs code layout section for custom template tags and filters:
The app should contain a templatetags directory, at the same level as models.py, views.py, etc. If this doesn’t already exist, create it - don’t forget the init.py file to ensure the directory is treated as a Python package.
So create a folder at same level as your view.py and name it templatetags. (Don't forget to add __init__.py inside). At the same level of that __init__.py add your nav_active.py and it should be ready to use. Like this:
yourapp/
__init__.py
models.py
views.py
templatetags/
__init__.py
nav_active.py

django crispy forms with jinja2

I want to use Jinja2 and Django crispy forms together, but when I load crispy_forms_tags in my template and trying to render form with this template tag, I get an error:
Encountered unknown tag 'crispy'. Jinja was looking for the following tags: 'endblock'. The innermost block that needs to be closed is 'block'.
My template code:
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="panel-heading"><h3>Registration</h3></div>
{% crispy form %}
{% endblock %}
How can I use this apps together?
Better solution:
templatetags.py
from crispy_forms.utils import render_crispy_form
from django_jinja import library
from jinja2 import contextfunction
#contextfunction
#library.global_function
def crispy(context, form):
return render_crispy_form(form, context=context)
In template:
{{ crispy(form) }}
Well, Geoffrey R.'s answer is a good approach, but the wrong method of jinja2 is used. Instead of render_crispy_form, it should be as_crispy_form. So, from crispy_forms.templatetags.crispy_forms_filters import as_crispy_form should be written at the beginning.
The corrected example jinja2.py file is as follows.
from crispy_forms.templatetags.crispy_forms_filters import as_crispy_form # this line is different
from django.contrib import messages
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from django.utils import translation
from jinja2 import Environment
def environment(**options):
env = Environment(
extensions=["jinja2.ext.i18n", "jinja2.ext.with_"], **options
)
env.globals.update(
{
"get_messages": messages.get_messages,
"static": staticfiles_storage.url,
"crispy": as_crispy_form, # this line is different
"url": reverse,
}
)
env.install_gettext_translations(translation)
return env
Other steps are same with Geoffrey R.'s answer.
It works for me, with the following pip packages:
Django==3.1.2
django-crispy-forms==1.9.2
Jinja2==2.11.2
By the way, I'm not using the django-jinja pip package.
Thanks Geoffrey R. for his great idea.
I found an easy, but not completed way to "hack in".
use 'django-jinja' package to register new filters;
in the filter file, define a crispy filter, which looks like:
from crispy_forms.templatetags.crispy_forms_filters import as_crispy_form
def crispy(form):
return as_crispy_form(form, 'Bootstrap3', form.helper.label_class, form.helper.field_class)
In the form Jinja template, I have to write more code than direct crispy tag in django template:
<form id='id_form' class="form-horizontal" method='post'>
{% csrf_token %}
{{form.media}}
{{ form|crispy() }}
<div>
<input type="submit" name="submit" value="Submit" class="btn btn-primary" id="submit-id-submit">
</div>
</form>
If anyone finds a more effective way to crispy forms, please let me know.
I have been struggling with this issue of yours and the given answer, although they might be relevant a few years ago, did not satisfy me.
So I went myself building a hack to make crispy forms work with Django 3.0.2 (I did not test the others versions, please let me know guys if there is any issue on any other version).
Install the relevant packages to make crispy forms work with Jinja2 :
pip install django-crispy-forms django-jinja
In your project directory (and NOT your application directory), make sure you have a jinja2.py file with:
from crispy_forms.utils import render_crispy_form
from django.contrib import messages
from django.contrib.staticfiles.storage import staticfiles_storage
from django.urls import reverse
from django.utils import translation
from jinja2 import Environment
def environment(**options):
env = Environment(
extensions=["jinja2.ext.i18n", "jinja2.ext.with_"], **options
)
env.globals.update(
{
"get_messages": messages.get_messages,
"static": staticfiles_storage.url,
"crispy": render_crispy_form, # THIS LINE IS RELEVANT
"url": reverse,
}
)
env.install_gettext_translations(translation)
return env
Make sure that your jinja2 template engine loads this option in your django's settings:
TEMPLATES = [
{
"BACKEND": "django.template.backends.jinja2.Jinja2",
"DIRS": [os.path.join(BASE_DIR, "jinja2")],
"APP_DIRS": True,
"OPTIONS": {"environment": "your_django_project.jinja2.environment"},
},
# ...
Note the "crispy": render_crispy_form, which will help you use crispy forms in your jinja2 templates.
Then, in your template, you can use:
{{ crispy(form) }}

TypeError using custom template tag

I get 'module' object is not callable using the following template tag:
projectname/controlpanel/templatetags/__init__.py (blank file)
projectname/controlpanel/templatetags/md_to_html.py
from django import template
from markdown import markdown
register = template.Library()
#register.filter(name='to_html')
def to_html(md):
return markdown(md)
In one of my views, I return {'campaign': campaign}, with campaign being an instance of a model with a description TextField.
<div class="span8" id="editor2">
{{ selected_campaign.description|to_html }}
</div>
add this to INSTALLED_APPS
'django.contrib.markup',
copy markdown(http://pypi.python.org/pypi/Markdown) to your django project directory
then use
{% load markup %}
<div class="span8" id="editor2">
{{ selected_campaign.description|markdown:"safe" }}
</div>
Update:
django.contrib.markup is deprecated in Django 1.5. Here is a simple replacement for the markdown filter.
remove line 'django.contrib.markup', from INSTALLED_APPS
steps to create a template tag:
add a folder templatetags in any of your app folder.
inside templatetags folder add an empty file __init__.py
add markup.py inside templatetags with these codes:
from django import template
from django.utils.safestring import mark_safe
import markdown as mkdn
register = template.Library()
#register.filter
def markdown(value,smode=None):
return mark_safe(mkdn.markdown(value, safe_mode='escape'))
Well I do not why but markdown seems does not work inside a function in django for example I typed this in django shell (python manage.py shell):
from markdown import markdown
def yes():
return markdown("YES")
and it gives me the next error:
NameError: global name 'markdown' is not defined
and this seems to work
def yes():
from markdown import markdown
return markdown("YES")
outside django shell the first method works correctly hope this helps!