How to set first default rows/values in django admin's inline?
class Employee(models.Model):
username = models.CharField(_('Username'), max_length=150, null=False, blank=False)
email = models.CharField(_('Email'), max_length=150, null=False, blank=False)
class Details(models.Model):
employee = models.ForeignKey(Employee, verbose_name=_('Employee'), blank=False, null=False)
label = models.CharField(_('Label'), max_length=150, null=False, blank=False)
value = models.CharField(_('Value'), max_length=150, null=False, blank=False)
class DetailsFormset(forms.models.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.initial = [
{ 'label': 'first name'},
{'label': 'last name'},
{'label': 'job',}]
super(DetailsFormset, self).__init__(*args, **kwargs)
class DetailsInline(admin.TabularInline):
model = Details
formset = DetailsFormset
fieldsets = [
['', {'fields': ['employee', 'label', 'value']}]
]
class EmployeeAdmin(admin.ModelAdmin):
inlines = [DetailsInline]
but this row doesn't work
self.initial = [
{ 'label': 'first name'},
{'label': 'last name'},
{'label': 'job',}]
How do I set default values using django admin?
from django.utils.functional import curry
class DetailsInline(admin.TabularInline):
model = Details
formset = DetailsFormset
extra = 3
def get_formset(self, request, obj=None, **kwargs):
initial = []
if request.method == "GET":
initial.append({
'label': 'first name',
})
formset = super(DetailsInline, self).get_formset(request, obj, **kwargs)
formset.__init__ = curry(formset.__init__, initial=initial)
return formset
From here: Pre-populate an inline FormSet?
If what you need is to define default values for the new forms that are created you can redefine the empty_form property of a InlineFormSet:
class MyDefaultFormSet(django.forms.models.BaseInlineFormSet):
#property
def empty_form(self):
form = super(MyDefaultFormSet, self).empty_form
# you can access self.instance to get the model parent object
form.fields['label'].initial = 'first name'
# ...
return form
class DetailsInline(admin.TabularInline):
formset = MyDefaultFormSet
Now, every time you add a new form it contains the initial data you provided it with. I've tested this on django 1.5.
I tried many suggestions from Stackoverflow (Django=4.x), not working for me.
Here is what I did.
class MilestoneFormSet(forms.models.BaseInlineFormSet):
model = Milestone
def __init__(self, *args, **kwargs):
super(MilestoneFormSet, self).__init__(*args, **kwargs)
if not self.instance.pk:
self.initial = [
{'stage': '1.Plan', 'description': 'Requirements gathering', },
{'stage': '2.Define', 'description': 'Validate requirement', },
]
class MilestoneInline(admin.TabularInline):
model = Milestone
formset = MilestoneFormSet
def get_extra(self, request, obj=None, **kwargs):
extra = 0 #default 0
if not obj: #new create only
extra = 2 #2 records defined in __init__
return extra
I hope this works for everyone.
To provide a static default for all instances in the inline, I found a simpler solution that just sets it in a form:
class DetailsForm(django_forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['label'] = 'first_name'
class DetailsInline(admin.TabularInline):
form = DetailsForm
# ...
I think this doesn't work for the OP's particular case because each form has a different value for the 'label' field, but I hope it can be useful for anyone coming to this page in the future.
Related
I am currently trying to improve my form by restricting choices inside an input (user can choose only their own tags).
tag name / user name
I tried to do that inside the get/post function :
def post(self, request, *args, **kwargs):
form = DateInputForm(request.POST, limit_choices_to={'tags__user': request.user})
def get(self, request, *args, **kwargs):
form = DateInputForm(limit_choices_to={'tags__user': request.user})
(1) I get an error.
BaseModelForm.init() got an unexpected keyword argument 'limit_choices_to'
My form :
class DateInputForm(ModelForm):
class Meta:
model = Task
# exclude = ('user',)
fields = ['user', 'title', 'description', 'date_to_do', 'complete', 'tags']
widgets = {
'date_to_do': forms.DateTimeInput(format='%Y-%m-%dT%H:%M',
attrs={'type': 'datetime-local', 'class': 'timepicker'}),
}
My view :
class TaskUpdate(LoginRequiredMixin, UpdateView):
model = Task
template_name = "tasks/task_form.html"
form_class = DateInputForm
Tag model :
class Tag(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
tag_name = models.CharField(max_length=200, null=True, blank=False, default='')
Globally: the goal is to limit the tags that can be chosen by the user in my task form (with the tag input); currently, a user can choose another user's tag, which is not what I want.
I think the easiest way to do this is to override the constructor of your form, as shown in this answer: https://stackoverflow.com/a/1969081/18728725
Update:
Here is Wamz's solution:
def __init__(self, *args, **kwargs):
super(DateInputForm, self).__init__(*args, **kwargs)
if 'instance' in kwargs:
new_kwargs = kwargs.get('instance')
self.fields['tags'].queryset = Tag.objects.filter(user=new_kwargs.user.id)
Warning, that's not working in the createView
I am building a notification system for a company, where admin users can create Projects and add users to them. The Project model has 9 attributes but I only want to show 3 or 4 fields when a Project is created, but show them all when an existing Project is updated.
This change will only need to be reflected on the Django admin site, so I have extended the ProjectAdmin with my own ProjectForm, where I extend the init method to check if it is a new instance and if so remove certain fields.
# models.py
class Project(models.Model):
project_number = models.IntegerField()
name = models.CharField(max_length=100)
permit = models.CharField(max_length=100, blank=True, default='')
is_active = models.BooleanField(default=True)
users = models.ManyToManyField(CustomUser, blank=True, related_name='project_users')
# add a default
levels = models.ManyToManyField('Level', blank=True, related_name='project_levels')
total_contract_hours = models.IntegerField(default=0, blank=True, verbose_name='Total Design Hours')
hours_used = models.IntegerField(default=0, blank=True, verbose_name='Total Design Hours Used')
notes = models.ManyToManyField('notes.ProjectNote', related_name='core_project_notes', blank=True)
history = HistoricalRecords()
def __str__(self):
ret_str = "{} {}".format(self.project_number, self.name)
if self.permit:
ret_str += " | Permit: {}".format(self.permit)
return ret_str
# admin.py
class ProjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
attrs = {'class': 'form-control', 'required': True}
if self.instance and self.instance.pk is None:
# creating project
exclude = ['is_active', 'users', 'levels', 'hours_used', 'notes']
for field in exclude:
try:
del self.fields[field]
except ValueError:
print('{} does not exist'.format(field))
for field in self.fields.values():
field.widget.attrs = attrs
class Meta:
model = Project
fields = ['project_number', 'name', 'total_contract_hours']
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
fields = ['project_number', 'name', 'permit', 'is_active', 'users', 'levels', 'total_contract_hours', 'hours_used', 'notes']
As I stated I only want basic Project fields on creation, but show all attributed when updating existing Project. With just these changes, I now get a KeyError:
KeyError: "Key 'is_active' not found in 'ProjectForm'. Choices are:
name, permit, project_number, total_contract_hours."
However, when I print the available fields it returns an OrderedDict with all of the model attributes as keys. What am I doing wrong? Thanks!
I figured it out, the field must be in listed in Meta and then you just set the field to be a hidden field.
class ProjectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
print("Adding project")
if not self.instance or self.instance.pk is None:
for name, field in self.fields.items():
if name in ['design_manager', ]:
field.widget = forms.HiddenInput()
class Meta:
model = Project
fields = ['project_number', 'name', 'design_manager', 'total_contract_hours']
class ProjectAdmin(admin.ModelAdmin):
form = ProjectForm
def save_model(self, request, obj, form, change):
obj.design_manager = request.user
super().save_model(request, obj, form, change)
I want to make a queryset on a field in an inline formset.. I have Inovice and Product models and InvoiceDetails model to link the manytomany relation between them.
here are the models:
class Invoices(models.Model):
"""The Invoice Creation Class."""
invoice_number = models.CharField(
_('invoice_number'), max_length=100, unique=True, null=True)
....
class Products(models.Model):
"""Product Creation Class."""
company = models.ForeignKey(Company, default=1)
barcode = models.CharField(_('barcode'), max_length=200, null=True)
....
class InvoiceDetail(models.Model):
invoice = models.ForeignKey(Invoices, related_name='parent_invoice')
product = models.ForeignKey(Products, related_name='parent_product')
quantity_sold = models.IntegerField(_('quantity_sold'))
...
when crearting an invoice i have inline formsets for the products which create an invoice details for every product.. now i want to filter the products that appear for the user to choose from them by the company. i searched a lot on how to override the queryset of inline formsets but found nothing useful for my case.
my forms:
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoices
fields = ('customer', 'invoice_due_date', 'discount', 'type')
def __init__(self, *args, **kwargs):
self.agent = kwargs.pop('agent')
super(InvoiceForm, self).__init__(*args, **kwargs)
def clean_customer(self):
.....
def clean(self):
......
class BaseDetailFormSet(forms.BaseInlineFormSet):
def clean(self):
......
DetailFormset = inlineformset_factory(Invoices,
InvoiceDetail,
fields=('product', 'quantity_sold'),
widgets= {'product': forms.Select(
attrs={
'class': 'search',
'data-live-search': 'true'
})},
formset=BaseDetailFormSet,
extra=1)
and use it in the views like that:
if request.method == 'POST':
invoice_form = InvoiceForm(
request.POST, request.FILES, agent=request.user)
detail_formset = DetailFormset(
request.POST)
.......
else:
invoice_form = InvoiceForm(agent=request.user)
detail_formset = DetailFormset()
so, how can it filter the products that show in detail_formset by company?
I solved it be passing the user to init and loop on forms to override the queryset.
def __init__(self, *args, **kwargs):
self.agent = kwargs.pop('agent')
super(BaseDetailFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.fields['product'].queryset = Products.objects.filter(company=self.agent.company)
in views:
detail_formset = DetailFormset(agent=request.user)
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 want to be able to add fields to django admin form at runtime. My model and form:
#admin.py
class SitesForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SitesForm, self).__init__(*args, **kwargs)
self.fields['mynewfield'] = forms.CharField()
class SitesAdmin(admin.ModelAdmin):
form = SitesForm
admin.site.register(Sites,SitesAdmin)
#model.py
class Sites(models.Model):
url = models.URLField(u'URL')
is_active = models.BooleanField(default=True, blank=True)
is_new = models.BooleanField(default=False, blank=True)
group = models.ForeignKey('SitesGroup')
config = models.TextField(blank=True)
Field mynewfield isn't displayed in form. Why?
You shouldn't be adding a new field to your form in that way, you can just do it as you would any other field and the form will contain both the Model's original fields and your new fields:
class SitesForm(forms.ModelForm):
mynewfield = forms.CharField(max_length=255, blank=True)
class Meta:
model = Sites
class SitesAdmin(admin.ModelAdmin):
form = SitesForm
admin.site.register(Sites, SitesAdmin)
Edit: Sorry, should have read what you had written a little better. If you want a dynamic field like that, then you need to do the following and it will do exactly what you want:
class SitesForm(forms.ModelForm):
class Meta:
model = Sites
def __init__(self, *args, **kwargs):
self.base_fields['mynewfield'] = forms.CharField(max_length=255, blank=True)
super(SitesForm, self).__init__(*args, **kwargs)
class SitesAdmin(admin.ModelAdmin):
form = SitesForm
admin.site.register(Sites, SitesAdmin)
It's the base_fields that gets composed by the metaclass that holds the fields that the form will use.
Solution:
class AdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(AdminForm, self).__init__(*args, **kwargs)
self.fields.insert(1, 'myfield', forms.CharField())
class MyAdmin(admin.ModelAdmin):
form = AdminForm
def get_fieldsets(self, request, obj=None):
return (
(None, {
'fields': (..., 'myfield',),
}),
)