I have a model that has a CharField and in the admin I want to add choices to the widget. The reason for this is I'm using a proxy model and there are a bunch of models that share this CharField but they each have different choices.
class MyModel(MyBaseModel):
stuff = models.CharField('Stuff', max_length=255, default=None)
class Meta:
proxy = True
class MyModelAdmin(admin.ModelAdmin):
fields = ('stuff',)
list_display = ('stuff',)
admin.site.register(MyModel, MyModelAdmin)
For this model I want to use MY_CHOICES in MyModelAdmin.
Do I override a widget? Do I need to override the whole form?
from django.contrib import admin
from django import forms
class MyModel(MyBaseModel):
stuff = models.CharField('Stuff', max_length=255, default=None)
class Meta:
proxy = True
class MyModelForm(forms.ModelForm):
MY_CHOICES = (
('A', 'Choice A'),
('B', 'Choice B'),
)
stuff = forms.ChoiceField(choices=MY_CHOICES)
class MyModelAdmin(admin.ModelAdmin):
fields = ('stuff',)
list_display = ('stuff',)
form = MyModelForm
admin.site.register(MyModel, MyModelAdmin)
See: https://docs.djangoproject.com/en/dev/ref/forms/fields/#choicefield
You don't need a custom form.
This is the minimum you need:
# models.py
from __future__ import unicode_literals
from django.db import models
class Photo(models.Model):
CHOICES = (
('hero', 'Hero'),
('story', 'Our Story'),
)
name = models.CharField(max_length=250, null=False, choices=CHOICES)
# admin.py
from django.contrib import admin
from .models import Photo
class PhotoAdmin(admin.ModelAdmin):
list_display = ('name',)
admin.site.register(Photo, PhotoAdmin)
You can override formfield_for_choice_field() that way you don't need to create a new form.
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_choice_field(self, db_field, request, **kwargs):
if db_field.name == 'status':
kwargs['choices'] = (
('accepted', 'Accepted'),
('denied', 'Denied'),
)
if request.user.is_superuser:
kwargs['choices'] += (('ready', 'Ready for deployment'),)
return super().formfield_for_choice_field(db_field, request, **kwargs)
See formfield_for_choice_field
You need to override the form the ModelAdmin is going to use:
class MyForm(forms.ModelForm):
stuff = forms.CharField('Stuff', max_length=255, choices=MY_CHOICES, default=None)
class Meta:
model = MyModel
fields = ('stuff', 'other_field', 'another_field')
class MyModelAdmin(admin.ModelAdmin):
fields = ('stuff',)
list_display = ('stuff',)
form = MyForm
If you need your choices to be dynamic, maybe you could do something similar to:
class MyForm(forms.ModelForm):
stuff = forms.CharField('Stuff', max_length=255, choices=MY_CHOICES, default=None)
def __init__(self, stuff_choices=(), *args, **kwargs):
# receive a tupple/list for custom choices
super(MyForm, self).__init__(*args, **kwargs)
self.fields['stuff'].choices = stuff_choices
and in your ModelAdmin's __init__ define what MY_CHOICES is going to be and assign the form instance there instead:
Good luck! :)
in Gerard's answer, if you keep :
def __init__(self, stuff_choices=(), *args, **kwargs):
then when you will try to add new model from admin, you will always get 'This field is required.' for all required fields.
you should remove stuff_choices=() from initialization:
def __init__(self,*args, **kwargs):
You need to think of how you are going to store the data at a database level.
I suggest doing this:
Run this pip command: pip install django-multiselectfield
In your models.py file:
from multiselectfield import MultiSelectField
MY_CHOICES = (('item_key1', 'Item title 1.1'),
('item_key2', 'Item title 1.2'),
('item_key3', 'Item title 1.3'),
('item_key4', 'Item title 1.4'),
('item_key5', 'Item title 1.5'))
class MyModel(models.Model):
my_field = MultiSelectField(choices=MY_CHOICES)
In your settings.py:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
#.....................#
'multiselectfield',
)
Watch the MAGIC happen!
Source:
https://pypi.python.org/pypi/django-multiselectfield
Below a solution that works immediately with Postgres' special ArrayField:
# models.py
class MyModel(models.Model):
class Meta:
app_label = 'appname'
name = models.CharField(max_length=1000, blank=True)
ROLE_1 = 'r1'
ROLE_2 = 'r2'
ROLE_3 = 'r3'
ROLE_CHOICES = (
(ROLE_1, 'role 1 name'),
(ROLE_2, 'role 2 name'),
(ROLE_3, 'role 3 name'),
)
roles = ArrayField(
models.CharField(choices=ROLE_CHOICES, max_length=2, blank=True),
default=list
)
# admin.py
class MyModelForm(ModelForm):
roles = MultipleChoiceField(choices=MyModel.ROLE_CHOICES, widget=CheckboxSelectMultiple)
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
list_display = ("pk", "name", "roles")
(Django 2.2)
Related
I'm using Django's CreateView in order to fill a form and I want some of the fields to be automatically filled in, looking for ides how I could that. the fields that I want to be filled in automatically are company, recruiter and date
this is what the views file looks like:
class CreateNewJobForm(CreateView):
model = Job
fields = (
'title', 'company', 'recruiter', 'job_type', 'work_from', 'description', 'city', 'address', 'title_keywords',
'date_created')
template_name = 'create_new_job_form.html'
success_url = '/job_created_successfully'
def form_valid(self, form):
form.instance.recruiter = self.get_name()
return super(CreateNewJobForm, self).form_valid(form)
and this is what the models file looks like:
class Recruiter(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)
name = models.CharField(max_length=255)
company = models.ForeignKey(Company, on_delete=models.RESTRICT, related_name='recruiters')
email = models.EmailField(max_length=255)
phone_number = models.CharField(max_length=15, blank=True)
Something like that should work just fine :
def form_valid(self, form):
form.instance.user = self.request.user # assuming you want the current login user to be set to the user
return super(CreateNewJobForm, self).form_valid(form)
It is just an example but in short you can access attributes of your model by accessing the instance of your form like that form.instance.yourfield
Automatically assign the value
We can assign a value without showing this in the form. In that case, you remove the company, recruiter, and date_created fields from the fields, and fill these in in the form_valid method:
from django.contrib.auth.mixins import LoginRequiredMixin
class CreateNewJobForm(LoginRequiredMixin, CreateView):
model = Job
fields = ('title', 'job_type', 'work_from', 'description', 'city', 'address', 'title_keywords')
template_name = 'create_new_job_form.html'
success_url = '/job_created_successfully'
def form_valid(self, form):
recruiter = form.instance.recruiter = self.request.user.recruiter
form.instance.company_id = recruiter.company_id
return super().form_valid(form)
for the date_created, you can work with the auto_now_add=True parameter [Django-doc] of the DateTimeField:
class Job(models.Model):
# …
date_created = models.DateTimeField(auto_now_add=True)
Provide an initial value
We can also provide an initial value for the form by overriding the .get_initial() method [Django-doc]:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.timezone import now
class CreateNewJobForm(LoginRequiredMixin, CreateView):
model = Job
fields = ('title', 'job_type', 'work_from', 'description', 'city', 'address', 'title_keywords', 'company', 'recruiter', 'date_created')
template_name = 'create_new_job_form.html'
success_url = '/job_created_successfully'
def get_initial(self):
recruiter = self.request.user.recruiter
return {
'recruiter': recruiter,
'company': recruiter.company,
'date_created': now()
}
i have model like this
### models.py
class Pizza(models.Model):
name = models.CharField()
price = models.IntegerField()
have_recipe = models.BooleanField()
### admin.py
admin.register(Pizza)
class PizzaAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price')
exclude = ('have_recipe',)
when I enter localhost:8000/admin/pizza i can see all of pizza objects,
but, I want to make admin pizza list show only have_recipe=True objects and nobody can't control this filter in admin page
is there any solution??
You can override the get_queryset(…) method [Django-doc] and work with:
# admin.py
#admin.register(Pizza)
class PizzaAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'price')
exclude = ('have_recipe',)
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
have_recipe=True
)
You should also use #admin.register(Pizza) as a decorator, so with a leading #.
Hi everyone Y create my own app in djando CMS, now I want to add my own class and id's to my field.. y try this, but I don't obtain any successful result.
in my model.py I have this
class Entry(models.Model):
TYPES_CHOICES = (
('none', 'not specified'),
('s', 'Series'),
('mb', 'Multiples Bar'),
('b', 'Bar suggestion'),
)
app_config = AppHookConfigField(HealthConfig)
code = models.CharField(blank=True, default='', max_length=250)
url_suggestion = models.CharField(blank=True, default='', max_length=250, verbose_name="URL for Suggestion" )
health_placeholder = PlaceholderField('health_info')
objects = AppHookConfigManager()
def __unicode__(self):
return self.url
class Meta:
verbose_name_plural = 'entries'
and now in my form.py I have this
from django import forms
from .models import Entry
class EntryForm(forms.ModelForm):
class Meta:
model = Entry
fields = '__all__'
def __init__(self, *args, **kwargs):
super(EntryForm, self).__init__(*args, **kwargs)
self.fields['code'].widget.attrs={
'id': 'my_code',
'class': 'code_class',
}
finally my admin.py is like this
from django.contrib import admin
from cms.admin.placeholderadmin import PlaceholderAdminMixin
from .cms_appconfig import HealthConfig
from .models import Entry
from .forms import EntryForm
from aldryn_apphooks_config.admin import ModelAppHookConfig, BaseAppHookConfig
class EntryAdmin(ModelAppHookConfig, PlaceholderAdminMixin, admin.ModelAdmin):
# pass
fieldsets = (
('General data', {
'fields':('app_config','chart', 'url',('count', 'code', 'start'))
}),
('Suggestion',{
'classes':('collapse', 'suggestion',),
'fields':('url_suggestion',('key1_suggestion_name','key1_suggestion'),('key2_suggestion_name','key2_suggestion'), 'primary_suggestions')
}),
)
list_display =('app_config' ,'url', 'chart');
list_filter = (
'app_config',
)
form = EntryForm
class Media:
js = ('health/js/admin/healthAdmin.js',)
css = {
'all': ('health/css/admin/admin_area.css',)
}
admin.site.register(Entry, EntryAdmin)
any idea is I missing something, after that, I do a migrate of the component again.
Thanks in advance!
You can specify a custom form for admin using the form attribute of ModelAdmin.
So using the example from the docs linked below, that would look like;
from django import forms
from django.contrib import admin
from myapp.models import Person
class PersonForm(forms.ModelForm):
class Meta:
model = Person
exclude = ['name']
class PersonAdmin(admin.ModelAdmin):
exclude = ['age']
form = PersonForm
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form
So in your admin.py you'd need something like;
from .forms import EntryForm
class EntryAdmin(admin.ModelAdmin):
form = EntryForm
I am trying to set the fieldorder of my form. but somehow it just stays in alphabetical order. Anyone has some suggestions? i tried class Meta: fields = ["field", "field"] and adding a keyOrder in the init
form:
class HangarFilterForm(forms.Form):
FIELDS = [
("", ""),
("warp", "Warp"),
("cargo_space", "Cargo Space"),
("smuggle_bay", "Smuggle Bay"),
("dock", "Dock/Undock"),
("enter_warp", "Enter Warp"),
("fuel_bay", "Fuel Bay"),
("fuel_cost", "Fuel Cost"),
]
PER_PAGE = [
(10, ""),
(5, "5 ships"),
(10, "10 ships"),
(25, "25 ships"),
(50, "50 ships"),
]
field_1 = forms.ChoiceField(choices=FIELDS, label="1st attribute", required=False)
field_2 = forms.ChoiceField(choices=FIELDS, label="2nd attribute", required=False)
per_page = forms.ChoiceField(choices=PER_PAGE, required=False)
def __init__(self, *args, **kwargs):
super(HangarFilterForm, self).__init__(*args, **kwargs)
self.fields['planet'] = forms.ChoiceField(
choices=[("", "")] + [ (o.id, o.name) for o in lanet.objects.all().order_by("name")],
required=False)
self.fields['type'] = forms.ChoiceField(
choices=[("", "")] + [ (o[0], o[1]) for o in ShipTemplate.SHIP_TYPES], required=False)
self.fields.keyOrder = ["planet", "type", "field_1", "field_2", "per_page"]
In Django 1.9, new way of forcing the order of form's fields has been added : field_order.
Take a look (link to version 1.9):
https://docs.djangoproject.com/en/1.9/ref/forms/api/#django.forms.Form.field_order
(and a link to dev version):
https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.field_order
Find below a short example (using Django 1.9)
models.py:
from django.db import models
class Project(models.Model):
end_date = models.DateField(verbose_name='End date',
blank=True)
start_date = models.DateField(verbose_name='Start date',
blank=True)
title = models.CharField(max_length=255,
blank=False,
verbose_name='Title')
def __str__(self):
return self.title
forms.py
from django.forms import ModelForm, DateTimeField, SelectDateWidget
from XXX.models import Project
class ProjectForm(ModelForm):
class Meta:
model = Project
fields = '__all__'
start_date = DateTimeField(widget=SelectDateWidget)
end_date = DateTimeField(widget=SelectDateWidget)
field_order = ['start_date', 'end_date']
In this example the fields will be rearranged to:
start_date <== using the list in the form class
end_date <== using the list in the form class
title <== not mentioned in the list, thus using the default ordering
I tried setting fields in Meta part of form in django 2.1 and it also did the trick:
class MyForm(forms.ModelForm):
...
class Meta:
model = Contact
fields = ('field_1', 'field_2', 'field_3', 'field_4',)
This is some code that I've done in the past to rearrange the field order in forms that has worked; you could probably put this into a mixin for use elsewhere. Let me know how it goes.
from django.utils.datastructures import SortedDict
class HangarFilterForm(forms.Form):
ordered_field_names = ['planet', 'type', 'field_1', 'field_2', 'per_page']
def __init__(self, *args, **kwargs):
super(HangarFilterForm, self).__init__(*args, **kwargs)
# Your field initialisation code
self.rearrange_field_order()
def rearrange_field_order(self):
original_fields = self.fields
new_fields = SortedDict()
for field_name in self.ordered_field_names:
field = original_fields.get(field_name)
if field:
new_fields[field_name] = field
self.fields = new_fields
If you want to keep track of the original file order for some reason, you can just change original_fields to self.original_fields in rearrange_field_order.
Might be a little bit off topic. Using django crispy forms and their Layout objects can help a great deal with formatting forms the way you exactly want. Which includes rearranging the field order.
A sample to illustrate:
class UpdateUserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.layout = Layout(
Field('email'),
Field('quote'),
Field('website', placeholder="http://"),
Field('logo', template="user/logoInput.html"),
HTML('<label class="control-label">Other settings</label>'),
Field('emailVisible'),
Field('subscribeToEmails'),
Field('mpEmailNotif'),
Field('showSmileys'),
Field('fullscreen'),
)
class Meta:
model = ForumUser
fields = ('email', 'emailVisible', 'subscribeToEmails', 'mpEmailNotif',
'logo', 'quote', 'website', 'showSmileys', 'fullscreen')
I used the bspink's way with some improvements. You don't need to define the fields that you don't want to change their ordering. Define only what you want to put to the top as ordered in themself.
(Be sure you are using the Python 3.7+)
class SomeForm(forms.Form):
# define only you want to put top, no need to define all of them
# unless you need more specific ordering
ordered_field_names = ['planet', 'type']
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
# call initialization code
self.rearrange_field_order()
def rearrange_field_order(self):
# add defined fields first
new_fields = {field_name: self.fields.get(field_name) for field_name in self.ordered_field_names}
# then add others whose not defined in order list
for key, value in self.fields.items():
if key not in new_fields:
new_fields[key] = value
self.fields = new_fields
I have two tables related Quiz and Difficulty_level:
I have created inline in admin.py like this:
class DifficultyLevelInline(admin.TabularInline):
model = DifficultyLevel
and included in QuizAdmin
To arrange the list order, I would do:
list_display = ('name', 'description', 'publication_date', 'category', 'is_active', 'is_premium')
How can I add inlines in the list_display order. I want to display The DifficultyLevelInline before category.
Unfortunately this is not possible using the default template.
If you take a look at change_form template:
https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
You can see that inlines are always rendered after fieldsets.
One way to get around this would be to use other template:
class MyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'publication_date', 'category', 'is_active', 'is_premium')
inlines = (DifficultyLevelInline,)
change_form_template = "my_change_form.html"
Grapelli supports it: https://django-grappelli.readthedocs.org/en/latest/customization.html#rearrange-inlines
Basically, it uses a placeholder via fieldsets and then moves them HTML via JavaScript:
https://github.com/sehmaschine/django-grappelli/blob/master/grappelli/templates/admin/change_form.html#L90-96 (search for placeholder, if the lines do not match anymore).
The same can be done by injecting custom javascript yourself (with or without using fieldsets as placeholders).
Suppose here is your inline model:
# models.py
from django.contrib.auth.models import Group
class MoreGroup(models.Model):
group = models.OneToOneField(Group, on_delete=models.CASCADE, related_name='more_group')
explain = models.CharField(verbose_name="explain_info", max_length=64, blank=True, null=True)
active = models.BooleanField(verbose_name="is_actived", default=True, blank=True, null=True)
then do this:
# admin.py
from . import models
class MoreGroupInline(admin.StackedInline):
model = models.MoreGroup
can_delete = False
verbose_name_plural = 'more_info'
class MyGroupAdmin(GroupAdmin):
list_display = ['id', 'name', 'get_inline_info']
def get_inline_info(self, obj) -> str:
mg = models.MoreGroup.objects.filter(group=obj)
if mg.count():
return mg[0].explain
else:
return '-'
get_inline_info.short_description = 'explain_info'
admin.site.register(models.Group, MyGroupAdmin)
I found a solution here: https://blog.devgenius.io/django-admin-dynamic-inline-positioning-7208596479ce
class MyAdmin(admin.ModelAdmin):
list_display = ('name', 'description', 'difficulty_level_inline', 'publication_date', 'category', 'is_active', 'is_premium')
inlines = (DifficultyLevelInline,)
def difficulty_level_inline(self, *args, **kwargs):
context = getattr(self.response, 'context_data', None) or {}
inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)
return get_template(inline.opts.template).render(context, self.request)
def render_change_form(self, request, *args, **kwargs):
self.request = request
self.response = super().render_change_form(request, *args, **kwargs)
return self.response