Django - Rendering tooltips stored in the database in templates - django

I'm building a Django site and in various places I want to display a little question mark and if the user hovers over it, it should display a message. I think the best approach is to store the tooltip in the database like this:
class Tooltip(models.Model):
key = models.Charfield(max_length=20)
message = models.Charfield(max_length=300)
My question is what is the best way to get the tooltips in the templates? So far my idea is to create a custom template tag, which will do the lookup and return the result, but I haven't used template tags much and wondering if there is a problem with this approach?
{% tooltip 'message_key' %}

The Django documentation explains how to create template tags.
Could be as simple as:
from django import template
register = template.Library()
#register.simple_tag
def tooltip(key):
try:
tooltip = Tooltip.objects.get(key=key)
return tooltip.message
except Tooltip.DoesNotExist:
return '' # or return a message stating that the key does not exist.
You need to save this file in your app's templatetags/ directory, along with an __init__.py. You can then {% load "<appname>" %} in your template and use the tag like you suggested.
While you might not need a database to store these, if you do, at least add an index to the key field on your model.

Related

Can I limit the visible Collections in the Wagtail Admin Image Chooser?

I have a few Groups and Collections setup to take advantage of the Collections feature in wagtail.
I have limited collection A to Administrators only.
After logging in as a non-Administrator and clicking on the 'CHOOSE AN IMAGE' button to bring up the image chooser, there's a drop down for 'Collection' and it includes all of my collections, including the restricted collection A.
Is it possible to only show collections and images that the user owns similar to how the 'Images' menu item works?
Wagtail: 1.12.2
Django: 1.8.18
I know it has been a while since this question was asked but hopefully this answer helps some people.
Solution Overview
Wagtail 1.10 (as at this post, 2.10 is in development) introduced a helpful hook called construct_image_chooser_queryset. This hook will intercept the images before being returned to the image chooser modal (used across all of Wagtail admin) and lets a developer customise what images are returned.
The hook also has access to the request, from there you can work out the user and build up your image query accordingly.
The other part of your question involved the collections drop-down field, this requires a bit of Django template work but is doable without too much extra complexity. Any Wagtail admin template can be overridden with a custom template, simply name it using the existing template path.
The Django shared template (include) that is used to render the collection dropdown, as at posting is wagtail/admin/templates/wagtailadmin/shared/collection_chooser.html
Example Code
Note: the code is not a full solution as the question did not have the collection model changes but will be a good start hopefully.
1. Wagtail image chooser queryset hook
Based on the example in the hooks documentation
file: wagtail_hooks.py
from wagtail.core import hooks
#hooks.register('construct_image_chooser_queryset')
def show_my_uploaded_images_only(images, request):
# Example: Only show uploaded images based on current user
# actual filtering will need to be based on the collection's linked user group
images = images.filter(uploaded_by_user=request.user)
return images
2. collection chooser template override
2a file: ../templates/wagtailadmin/shared/collection_chooser.html
We want to override the template and redirect it to a custom one ONLY IF images are present in the template's context
{% extends images|yesno:"base/include/images_collection_chooser.html,wagtailadmin/shared/collection_chooser.html" %}
2b file: ../templates/base/include/images_collection_chooser.html
This template will only be used if there are images in the context
Here we revise the context to remove images and filter collections then render the original Wagtail admin template
{% load filter_with_permissions %}
{% with images=None %}
{% with collections=collections|filter_with_permissions:user %}
Images Only Collection Template:
{% include "wagtailadmin/shared/collection_chooser.html" %}
{% endwith %}
{% endwith %}
2c file: bakerydemo/base/templatetags/filter_with_permissions.py
Here we make the Django template tag that does the collection filtering
from django import template
register = template.Library()
#register.filter(name='filter_with_permissions')
def filter_with_permissions(collections, user):
""" filter the collections based on current user """
return collections.filter(name__startswith='B') # actual filter to be done

How to add a custom button or link beside a form field

I am using Django with crispy_forms third party library. I want to add a link beside a form field like some forms in Django admin app. How can I do this?
You've picked quite a complicated example that uses a method I wouldn't even recommend. But I'll try to explain how you see what you're seeing, and keep it short.
That is a AdminTimeWidget(forms.TimeInput) and a AdminDateWidget both nested in a AdminSplitDateTime(SplitDateTimeWidget(MultiWidget)). The MultiWidget part of this isn't really important, that's just how you bind two widgets together to provide one value (a datetime.datetime).
Here's what AdminTimeWidget looks like:
class AdminTimeWidget(forms.TimeInput):
#property
def media(self):
extra = '' if settings.DEBUG else '.min'
js = [
'vendor/jquery/jquery%s.js' % extra,
'jquery.init.js',
'calendar.js',
'admin/DateTimeShortcuts.js',
]
return forms.Media(js=["admin/js/%s" % path for path in js])
def __init__(self, attrs=None, format=None):
final_attrs = {'class': 'vTimeField', 'size': '8'}
if attrs is not None:
final_attrs.update(attrs)
super().__init__(attrs=final_attrs, format=format)
That adds a DateTimeShortcuts.js script to the page (in the way that Admin Widgets can, via the form media property) and it's that script that iterates input tags looking for date and time inputs.
There's a LOT of machinery involved to get that happening but again, in effect, it's just a bit of javascript that looks for a date/time input and adds the HTML client-side.
But you probably don't want to do that.
As I said, that's a very complicated widget, and in Admin where it's harder to alter things on the fly. If you want to write an Admin widget, you probably do want to go that way.
But if you already control the template, or a crispy layout, you could just bung in some HTML. Crispy has an HTML element that you can throw into layouts. This is well documented.
Or if you want a reusable widget, you could use a custom template. Since Django 1.11, Widgets use templates to render.
Create a widget, borrowing from an existing one to save time
from django.forms import widgets
class DateWithButtonWidget(widgets.DateInput):
template_name = 'widgets/date_with_button.html'
Customise the template with the HTML you want:
{% include "django/forms/widgets/input.html" %} <button>MY BUTTON</button>
Use that widget in your form:
class MyForm(forms.ModelForm):
fancydate = forms.DateField(widget=DateWithButtonWidget)
Of course, wiring that button to do something is all up to you. Using a fully-scripted option might be what you need after all.

How to handle transient data in Django model (or how to avoid a global variable)?

I have a tree of models defined in my Django app (e.g. 'a' is a top level model, which has many 'b's, which has many 'c's). I also have the Views/Templates that render these appropriately. For each one of these models I typically need to do a Database query based on the current logged in user.
For example, it's similar to how each user on stack overflow can mark a question with a star. If my model is the question, I would ask the model if the current user has this question starred and then render it appropriate in the template.
My first thought was to try to pass a parameter in the template (which I now know doesnt' work).
# template
{{ question.is_starred(request.user) }} # Can't work.
My second thought was to have some type of global variable (which I don't like on principle).
# model
class question (Models.model)
def _is_starred(self):
# Use a global variable to find out the current logged in user!
My third thought was to have the View tell the model the currently logged in user, but the trouble is, I have a tree of model objects and I think I'd have to load and set every model in the tree, even if I don't end up using them all. I assume that the objects are lazily loaded.
# view
def view_one_question(request, question_id):
q = Question.objects.get(pk=question_id)
q.SetCurrentlyLoggedInUser (request.user.id)
# !!!! But what about the other network of objects that the question has?
return render_to_response(...)
Any advice is appreciated. I'm new to Django and trying to start this project off with the best design possible.
Your first example is the right idea, but the wrong implementation. You can't pass parameters to methods in Django templates. The easiest way around this is a simple filter:
# yourapp/templatetags/somefile.py
from django import template
register = template.Library()
#register.filter
def is_starred_for_user(question, user):
return question.is_starred(user)
Then, in your template:
{% load somefile %}
{{ question|is_starred_for_user:request.user }}

Django Problem - trying to access data entered into a form and feed it through a different page

OK, so let me give you an overview first. I have this site and in it there is a form section. When you access that section you can view or start a new project. Each project has 3-5 different forms.
My problem is that I don't want viewers to have to go through all 3-5 pages to see the relevant information they need. Instead I want to give each project a main page where all the essential data entered into the forms is shown as non-editable data. I hope this makes sense.
So I need to find a way to access all that data from the different forms for each project and to feed that data into the new page I'll be calling "Main". Each project will have a separate main page for itself.
I'm pretty much clueless as to how I should do this, so any help at all would be appreciated.
Thanks
You could try this. After that, you could:
Try creating a model for each project. This is done in "models.py" of the application modules created by django-admin
Use views to show that data to people (on your Main page)
If you've already seen all that, then:
First, you should create a view for your main page. So if you have an application my_app, my_app/views.py should be like:
def main_page_view(request, project_name):
# Your code here
pass
Then, to use this, you'd modify urls.py and add in something like:
(r'^projects/(?:<project_name>[a-zA-Z0-9]+)', 'my_app.views.main_page_view'),
Also, you'd need models, which are created in models.py, by subclassing django.models.Model
EDIT: re-reading your question, I guess you need this
Data can be passed from a view to a template through the context.
So say you create a summary view...
def summary(request, *args, **kwargs):
In that view you can query the database using the model api and pass the result of that query into the template for rendering. I'm not sure what your models look like, but say you had a model that had a title and the owner (as a ForeignKey to user)...
class Project(models.Model):
title = models.CharField(max_length=250)
user = models.ForeignKey(User)
Your model will be obviously be different. In your view you could query for all of the models that belong to the current user...
def summary(request, *args, **kwargs):
projects = Project.objects.filter(user=request.user)
Once you've gathered that, you can pass in the query to the template rendering system...
def summary(request, *args, **kwargs):
projects = Project.objects.filter(user=request.user)
render_to_response('project_summary.html', {'projects': projects }, ... )
When you pass the query to the template, you've named it projects. From within the template you can access it by this name...
<body>
<table>
{% for project in projects %}
<tr><td>{{ project.title }}</td></tr>
{% endfor %}
</table>
</body>
(Notice also how you can access a property of the model from within the template as well.)

Django, custom tag... how?

I would like to make a django custom tag to display 10 entry titles from the category where the user is reading an article. How can I do this? I need to pass the category from the actual entry.
The best way to do this would be with an inclusion tag. This is a tag that renders a template fragment which renders the 10 related articles.
You just pass in the current article into the tag, and return the context for the template fragment - ie the related articles.
#register.inclusion_tag('related_articles.html')
def related_articles(article, count):
category = article.category
articles = category.article_set.exclude(id=article.id)[:count]
return {'articles': articles}
You'll need a related_articles.html file in your templates directory which outputs the articles. Then, to call it from your main template, you would just do
{% related_articles article 10 %}
where article is the name of the article object.
Why a custom tag? It's probably better, cleaner, to add a method to the article model, and call that from the template. Depending on your model, the method would be practically trivial.
Check here for making custom tags:
http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#howto-custom-template-tags
You would need to pass the relevant category object to the template context.