How are Django's form assets (Media class) served? - django

The "Form Assets" page of Django says
Django allows you to associate different files – like stylesheets and scripts – with the forms and widgets that require those assets. For example, if you want to use a calendar to render DateFields, you can define a custom Calendar widget. This widget can then be associated with the CSS and JavaScript that is required to render the calendar. When the Calendar widget is used on a form, Django is able to identify the CSS and JavaScript files that are required, and provide the list of file names in a form suitable for inclusion on your web page.
Okay, but what component of Django takes those form assets and actually puts them in the page?
I ask because I'm trying to use django-autocomplete-light (DAL) with django-bootstrap5 on a multi-select form with a many-to-many field. I've done all the installing, put the widget on my form field:
class SomeForm(forms.ModelForm):
class Meta:
model = Something
fields = ['things']
widgets = {
'things': autocomplete.ModelSelect2Multiple(
url='myapp:things-autocomplete')
}
I know the widget url is correct because if it's wrong, Django barks. I know the widget is being put onto the field because it changes (from listing all things to a blank square).
I can see the HTML for the form widget includes autocomplete-light stuff to multi-select "things" with "things" autocomplete:
...
<select name="things" class="form-select" required id="id_things"
data-autocomplete-light-language="en"
data-autocomplete-light-url="/things-autocomplete/"
data-autocomplete-light-function="select2" multiple>
</select>
...
However, I don't see anywhere that select2.js is included in this web page.
I used django-autocomplete-light's ModelSelect2Multiple, which has the Select2WidgetMixin that defines a media property with all kinds of good looking stuff:
return forms.Media(
js=(
'admin/js/vendor/select2/select2.full.js',
'autocomplete_light/autocomplete_light%s.js' % extra,
'autocomplete_light/select2%s.js' % extra,
) + i18n_file,
css={
'screen': (
'admin/css/vendor/select2/select2%s.css' % extra,
'admin/css/autocomplete.css',
'autocomplete_light/select2.css',
),
},
)
I do not see any of that good-looking stuff in my web page.
I'm running with debug=True (dev mode) for now, so I believe that static files should be served as long as I have the django.contrib.staticfiles app in my settings, which I do.
Which Django component should be serving up DAL's media, and how?
I'm using Django 4.1, django-autocomplete-light 3.9.4, django-bootstrap5 22.2.
P.S. I saw other questions that seemed related, but not exactly this situation.
EDIT: It looks like form assets are loaded in a template with {{form.media}}. Maybe I have to put that media in the right place (the HTML head)?

I got this to work by adding a script block, and adding jquery and the form media manually:
{% block head_script %}
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
{{form.media}}
{% endblock head_script %}
Not sure if there's a more automatic way to include widget media.

Related

Using placeholder tags in non-django CMS pages

In the Django CMS there's the {% placeholder 'content' %}. I tried to use it on a non-django-cms page, i.e., a detail-view page that comes from an apphook. However, when I switch to the structure view in the detail-view page and the placeholder does not seem to reflect. Is that's how it's supposed to work or is there a problem with my code? If it's how it's supposed to work is there a way to make placeholder appear in the page?
You can't use {% placeholder outside of CMS pages.
If you're on one of these pages, you can use a static placeholder. These will show the same content on any page where a static placeholder with the same name exists. So a good example of these is a footer, or header where you'd want it to be the same on all pages;
{% static_placeholder "footer" %}
Another thing you can use, good for your example of a detail page in an apphook, is a PlaceholderField on your models.
Take this example;
from django.db import models
from cms.models.fields import PlaceholderField
class Category(models.Model):
name = models.CharField(max_length=20)
description = PlaceholderField('category_description')
In your template you can then render this placeholder and it'll behave like a standard placeholder on a cms page;
{% load cms_tags %}
{% render_placeholder category_instance.description language 'en' %}
You can find docs for PlaceholderField here

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

Change the default widget for date fields in Django admin

How can I change the default widget for DateField instances in Django ADMIN?
I am aware of how to do this for a ModelForm - How do you change the default widget for all Django date fields in a ModelForm? - by providing formfield_callback that checks the field type and then provides a form field with the new widget.
However, some of my ModelAdmin's may already have a custom ModelForm, some haven't. I don't want to provide/change a ModelForm wherever there's a date field (that's the point).
A custom ModelAdmin which could be used for subclassing would be ok, though. How can a ModelAdmin change the date fields of its form, independent of whether a custom ModelForm has been provided or not?
Maybe a ModelAdmin with get_form() calling its super then setting the form's formfield_callback? Seems a little convoluted, not sure about the timing, and also then there's three things to be aware of if any ModelAdmin needs further customization...
P.S.: I'm asking this after https://stackoverflow.com/questions/8484811/jquery-datepicker-in-django-admin was erroneously closed for being an "exact duplicate" of the above-mentioned ModelForm question. I hope the difference is now clear.
Btw my personal motivation is the year navigation of django admin's default datepicker. Really bad for birthdays... :-) yes there's a three-year old ticket https://code.djangoproject.com/ticket/9388
I was able to customize the default date widget in the Django admin. I followed two tutorials (from Simple is Better than Complex and Django Custom Widget)
Write a custom widget in a file called widgets.py inside your Django app
from django.forms import DateInput
class CustomDatePickerInput(DateInput):
template_name = "app_name/widgets/custom_datepicker.html"
class Media:
css = {
"all": (
"https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/datepicker.min.css",
)
}
js = (
"https://code.jquery.com/jquery-3.4.1.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/datepicker.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/datepicker/0.6.5/i18n/datepicker.es-ES.min.js",
)
Notice that the CSS and JS files that you may need should be added in the internal Media class as shown in the code above. In this case it is adding styles for the custom date picker.
Define the template for your widget, for more date picker's options see here, put this file in app_name/templates/app_name/widgets/custom_datepicker.html or make sure that this template matches the value of template_name in your custom widget:
{% load i18n %}
{% include "django/forms/widgets/input.html" %}
{% get_current_language as LANGUAGE_CODE %}
<script>
$(function () {
const DATE_PICKER_OPTIONS = {
'en-us': {
format: 'yyyy-mm-dd',
language: ''
},
'es': {
format: 'dd/mm/yyyy',
language: 'es-ES'
}
};
let options = DATE_PICKER_OPTIONS['{{ LANGUAGE_CODE }}'];
$("input[name='{{ widget.name }}']").datepicker({
format: options.format,
language: options.language
});
});
</script>
You need to indicate what widget to use in date field when registering your model in the Django admin (use formfield_overrides)
#admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
formfield_overrides = {models.DateField: {"widget": CustomDatePickerInput}}
Once this is completed, you will have a custom widget for dates in the Django admin. Keep in mind that this is just a guide, you can go further by reviewing the two tutorials I indicated.
Note: the implementation of this widget is also supporting internationalization.
ModelAdmin can only change the widget of the form it renders, so it would be difficult to override the widgets of a custom form passed to it - at least I can't think of a clean way to do it.
I can see a few options for you, depending on how/what you want to customize.
Modify the javascript that does the magic for the widget. This seems easy enough if your change can be handled there. Simply provide your own js/calendar.js and js/admin/DateTimeShortcuts.js the same way you would override the admin's templates. This has the added bonus of being upgrade friendly; since you are not modifying the django code base; also the change would be immediate for all forms that use the widget.
Customize the built-in django widget to do what you want. This has the benefit of instantly working without any modifications of your existing code, but is not upgrade friendly unless you are careful about patching sources. The source is at django.contrib.admin.widgets
Provide your own custom widget - but then you are back to the problem of going through the code and manually overriding all widget instances with your custom one.
Use something like grappelli and modify the templates/js to do what you want. Added bonus here is that you'll get lots of other functionality for free (like the ability to have a dashboard, if you plug it in with django-admin-tools).
django uses jquery extensively in their admin and they have their own jquery namespace (django.jQuery); so if you plan on doing some custom widgets make sure you use the right namespace to avoid strange problems.

How can I adjust the appearance of the Django Admin?

Kindly help me How can i customize the text&charfield(length,breadth,font,colour) without using templates for the following code
class Record(models.Model):
Name = models.CharField(max_length=200,blank=True,null=True,help_text="Employee Name")
Empid = models.CharField(max_length=300,blank=True,null=True,help_text="Employee ID")
Salary = models.IntegerField(blank=True,null=True)
Bonus = models.IntegerField(blank=True,null=True)
In your admin.py:
class RecordAdmin(admin.ModelAdmin):
# your stuff...
class Media:
css = {
"all": ("my_styles.css",)
}
Documentation for class Media.
Now you should be able to override what you want in the my_styles.css file.
If you want a more general solution (not only for Record model but for many models), a good way to do it is to extend the base_site.html template to add your own CSS file to all admin pages.
{% block blockbots %}
<link rel="stylesheet" type="text/css" href="/media/css/admin_base.css" />
{{ block.super }}
{% endblock %}
I put it in blockbots instead of extrastyle to be sure it will be at the end so it will override all other stylesheets.
Documentation on overriding/extending admin templates.
It is not possible to modify the visual elements of the Django admin from the model. This would totally defeat the purpose of an MVC framework! Separation of concerns dictates that these changes would be done in the template.
If you want to change the text size you'll have to modify the stylesheets for the admin.
Maybe you need to create Forms from your Models and to customize them, here is the oficial doc.
Use widgets to customize with HTML attributes

How to give a column in the Django admin change_list a CSS class

I'd like to change the column widths in the list display of the Django admin.
Is it possible somehow to add a CSS classname to a column?
I'd preferably not overwrite the entire template to accomplish this.
In Django >= 1.6, CSS classes are included with list_display output:
"The field names in list_display will also appear as CSS classes in the HTML output, in the form of column-<field_name> on each <th> element. This can be used to set column widths in a CSS file."
While this feature is implemented in vers1.6 as StvnW said, for earlier versions you can do the following:
In admin.py:
class MyModelAdmin(admin.ModelAdmin):
# your code here
# specify a stylesheet just for the list view
class Media:
css = {'all': ('css/mymodel_list.css')}
In mymodel_list.css:
/* replace '5' with the column desired */
table#result_list td:nth-child(5) {
width: 15em;
}
Specifying table#result_list will apply this stylesheet only to the list view and won't affect the normal admin page for this model. Also, note that while django uses th for the first column of the model, it still counts for a td child.
There is an open ticket that addresses the need for specifying css classes for table columns in the change_list view.
That said ... in the description for the ticket there's a snippet that injects a custom stylesheet in your change_list-template:
{% extends "admin/change_list.html" %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="/css/poll_admin_changelist.css" />
{% endblock extrastyle %}
So you don't override the whole template, only the part (extrastyle) you need.
Now you could inject your own stylesheet and for example style your columns using the :nth-child-selector
Another option would be to wrap your specific fields in html which can be done using the list_display option. Here you could define a width or class for a wrapped element.
This does only makes sense though, if you want to control the width of limited set of fields