Change default widgets of Django to custom ones - django

My usecase: I want to use a different DateInput. But I want to reduce code duplication. I want all forms, which don't explicitly want a different DateInput widget, to use my custom widget.
Any change to solve this without monkey patching?
Example
models.py:
class MyModel(models.Model):
date=models.DateField()
forms.py:
class MyForm(forms.ModelForm):
class Meta:
model=MyModel
The above code should use my custom widget. I don't want to change the above models.py and forms.py, since there are many.

Unfortunately, I don't think you can get this working with your exact code listed above.
Without hacking django, essentially there are 2 parts to this. The first is creating a custom form field, and the second is defaulting your custom model field to your newly created form field.
To create your custom Form Field, you could override the existing django forms.DateField and update the widget.
# form_fields.py
from django.forms import DateField
from myapp.widgets import MyWidget
class MyDateFormField(DateField):
widget = MyWidget
And then after you have your form field created, you're going to have to override the django model field to default to your new form field
# fields.py
from django.db.models import DateField
from myapp.form_fields import MyDateFormField
class MyDateField(MyDateFormField):
def formfield(self, **kwargs):
defaults = {'form_class': MyDateFormField}
defaults.update(kwargs)
return super(DateField, self).formfield(**defaults)
You would then have your custom model field, which you would need to slightly change your code to use.
from myapp.fields import MyDateField
class MyModel(models.Model):
date=MyDateField()
It's not exactly what you were asking for (have to change the model field), but hopefully this gets you in the right direction.

Create your field
Create form that will use this field by default
import this form instead of default form, when you use it
If you're using it in admin:
create your own ModelAdmin that will use your form by default
use that instead of default ModelAdmin.

Related

Django admin: postgres DateTimeRangeField not displayed properly

Some of my models have postgres-specific django.contrib.postgres.fields.DateTimeRangeFields, and those fields are exposed in the corresponding admin panels. I expected that the ranges forms would consist of two Django-style datetime pickers, with a separate one for the date part and a separate part for the time part (just like the DateTimeField would). However, I get two text inputs which expect input in a very particular format. Is there anything I am missing or have to configure separately?
The relevant code is:
from django.contrib.postgres.fields import DateTimeRangeField
...
class MyModel(models.Model):
time_off = DateTimeRangeField()
admin:
#register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
pass
You are looking for SplitDateTimeWidget.
Simply change the admin part as:
class MyModelAdminForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'time_off': RangeWidget(SplitDateTimeWidget())
}
#register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelAdminForm
or use formfield_overrides to override the widget if you wish.

Django: Search many to many field while creating object

I've got a use case where I have multiple Ingredient that can be linked to a Recipe through the Django admin. Now I have around a hundred ingredients which makes it very difficult to select the ingredients in the following UI.
Is there a way to add a search field or something similar to the django admin for easier selection?
You have few choices.
1. filter_horizontal
With filter_horizontal, you can use horizontal m2m ui in admin. I prefer this way using m2m in admin.
class YourAdmin(admin.ModelAdmin):
filter_horizontal = ('m2m_field',)
...
And the result will be...
2. raw_id_fields docs
You can use raw_id_fields for using pop-up modal with your m2m fields.
It's bit useful when you have lots of m2m field. Also, it's easy to filter which m2m obj to add.
class YourAdmin(admin.ModelAdmin):
raw_id_fiedls = ('m2m_field',)
...
I suppose you want to filter over ingredients and select it one by one on admin UI
You can use django forms builtin CheckboxSelectMultiple
widget in place of SelectMultiple to make selection easy
from django import forms
from django.contrib import admin
class RecipeForm(forms.ModelForm):
class Meta(object):
model = Recipe
widgets = {
'Ingredient': forms.CheckboxSelectMultiple,
}
class RecipeAdmin(admin.ModelAdmin):
form = RecipeForm
admin.site.register(Recipe, RecipeAdmin)
Alternatively, you can use django-better-filter-widget
package if you want a search input on choices, Refer Github repo for
installation
It is a custom widget, created by overriding SelectMultiple widget of
django forms
from django import forms
from django.contrib import admin
from better_filter_widget import BetterFilterWidget
class RecipeForm(forms.ModelForm):
class Meta(object):
model = Recipe
widgets = {
'Ingredient': BetterFilterWidget(),
}
class RecipeAdmin(admin.ModelAdmin):
form = RecipeForm
admin.site.register(Recipe, RecipeAdmin)

Display a custom link in Django Admin edit item view?

Here's a piece of Django admin interface's instance edition form:
How should I change the underlying admin.ModelAdmin instance to make it contain an URL, like this?
Django makes this easy. Subclass ModelAdmin, add a custom method and then tell the Admin how to use it. Here's a sample admin.py:
from django.contrib import admin
from .models import Vendor
class VendorAdmin(admin.ModelAdmin):
readonly_fields = ['example_link']
def example_link(self, obj):
return 'link text'.format(obj.get_link()) # however you generate the link
example_link.allow_tags = True
admin.site.register(Vendor, VendorAdmin)
Here's the documentation that furthers explains readonly_fields, customizing the form label text with short_description, ordering, and how you can put this custom url method on the Model or ModelAdmin.

create model formsets from same model but with different model forms

I have an Image model in my Django project. Because of different types of Image I have created three ModelForms according to each type:
class Xray(ModelForm):
#extra_field: Choice Field with specific options for Xray
class Meta:
model = Image
class Internal(ModelForm):
#extra_field: Choice Field with specific options for Internal
class Meta:
model = Image
class External(ModelForm):
#extra_field: Choice Field with specific options for External
class Meta:
model = Image
Each ModelForm has a save logic implemented. I want to create a model formset one for each Image type but want to use the correct ModelForm for each type of Image. I won't use this formset for editing thus I always want it to be empty and have 5 forms(5 items). I can't seem to find in django docs where i can use a specific form for a formset. Only a specific formset (inherit from BaseModelFormSet)
Is it possible to use specific form for each model_formset?
You can do the following:
from django.forms.models import modelformset_factory
from someproject.someapp.models import Image
from someproject.someapp.forms import Internal
ImageFormSet = modelformset_factory(Image, form=Internal)
Here are the docs for modelform_factory, which don't mention the form argument. However, in the "Note" below the examples therein that the function delegates to formset_factory, which is documented to take the form argument. It's just a minor docs issue, and might be a good reason to create a fork of Django, make an update to the docs, and create a pull request.

Reorder users in django auth

I have a model that has a ForeignKey to the built-in user model in django.contrib.auth and I'm frustrated by the fact the select box in the admin always sorts by the user's primary key.
I'd much rather have it sort by username alphabetically, and while it's my instinct not to want to fiddle with the innards of Django, I can't seem to find a simpler way to reorder the users.
The most straightforward way I can think of would be to dip into my Django install and add
ordering = ('username',)
to the Meta class of the User model.
Is there some kind of monkeypatching that I could do or any other less invasive way to modify the ordering of the User model?
Alternatively, can anyone thing of anything that could break by making this change?
There is a way using ModelAdmin objects to specify your own form. By specifying your own form, you have complete control over the form's composition and validation.
Say that the model which has an FK to User is Foo.
Your myapp/models.py might look like this:
from django.db import models
from django.contrib.auth.models import User
class Foo(models.Model):
user = models.ForeignKey(User)
some_val = models.IntegerField()
You would then create a myapp/admin.py file containing something like this:
from django.contrib.auth.models import User
from django import forms
from django.contrib import admin
class FooAdminForm(forms.ModelForm):
user = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Foo
class FooAdmin(admin.ModelAdmin):
form = FooAdminForm
admin.site.register(Foo, FooAdmin)
Once you've done this, the <select> dropdown will order the user objects according to username. No need to worry about to other fields on Foo... you only need to specify the overrides in your FooAdminForm class. Unfortunately, you'll need to provide this custom form definition for every model having an FK to User that you wish to present in the admin site.
Jarret's answer above should actually read:
from django.contrib.auth.models import User
from django.contrib import admin
from django import forms
from yourapp.models import Foo
class FooAdminForm(forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwds):
super(FooAdminForm, self).__init__(*args, **kwds)
self.fields['user'].queryset = User.objects.order_by(...)
class FooAdmin(admin.ModelAdmin):
# other stuff here
form = FooAdminForm
admin.site.register(Foo, FooAdmin)
so the queryset gets re-evaluated each time you create the form, as opposed to once, when the module containing the form is imported.