I have Game model with ManyToMany relation on Taxonomy model witch has multiple types (PLATFORM, GENRE, FEATURE etc.)
class Game(models.Model):
taxonomy = models.ManyToManyField(Taxonomy)
class Taxonomy(models.Model):
TAXONOMY_ORDER = [
'PLATFORM',
'GAME_PROCESS',
'GRAPHICS',
'GENRE',
'CATEGORY',
'FEATURE'
]
type = models.CharField(choices=TAXONOMY_TYPES.items(), max_length=15)
I want to remove taxonomy field from admin and add separate MultiplueChoises field for each taxonomy type from TAXONOMY_ORDER
class GameAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(GameAdminForm, self).__init__(*args, **kwargs)
taxonomy_active = kwargs['instance'].taxonomy.all().values_list('id', flat=True)
for tax_type in Taxonomy.
self.fields['taxonomy_' + tax_type] = forms.MultipleChoiceField()
self.fields['taxonomy_' + tax_type].queryset = Taxonomy.objects.filter(type=tax_type)
self.Meta.fields.append('taxonomy_' + tax_type)
self.initial['taxonomy' + tax_type] = Taxonomy.objects.filter(
id__in=taxonomy_active,
type=tax_type
).values_list('id', flat=True)
class GameAdmin(admin.ModelAdmin):
form = GameAdminForm
def get_fieldsets(self, request, obj=None):
fieldsets = super(GameAdmin, self).get_fieldsets(request, obj)
for tax_type in Taxonomy.TAXONOMY_ORDER:
fieldsets[0][1]['fields'] += ['taxonomy_' + tax_type]
return fieldsets
I have two issues with this:
When I try add fields dynamicly I recieve an error
Unknown field(s) (taxonomy_FEATURE, taxonomy_PLATFORM, taxonomy_CATEGORY, taxonomy_GRAPHICS, taxonomy_GENRE, taxonomy_GAME_PROCESS) specified for Game. Check fields/fieldsets/exclude attributes of class GameAdmin.
When I try to add custom fields explicitly they are rendered blank
class GameAdminForm(forms.ModelForm):
taxonomy_PLATFORM = forms.MultipleChoiceField()
taxonomy_GAME_PROCESS = forms.MultipleChoiceField()
taxonomy_GRAPHICS = forms.ChoiceField()
taxonomy_GENRE = forms.MultipleChoiceField()
taxonomy_CATEGORY = forms.MultipleChoiceField()
taxonomy_FEATURE = forms.MultipleChoiceField()
def __init__(self, *args, **kwargs):
***__init__ stuff***
I don't have the rep for a comment, so this'll have to be an answer. I have been trying to solve the same problem for my own project, and the best solution I have found is this self-answered question by chadgh: How do I create and save dynamic fields in Django ModelAdmin?.
I have tried this method in my code. It works perfectly, and I think it does exactly what you're trying to accomplish. The only caveat is that in
def get_form(self, request, obj=None, **kwargs):
kwargs['fields'] = flatten_fieldsets(self.declared_fieldsets)
return super(PersonAdmin, self).get_form(request, obj, **kwargs)
self.declared_fieldsets is deprecated as of Django 1.7. I used self.fieldsets instead and it worked fine.
The main difficulty is that ModelAdmin normally gets the fields from the form class before it's instantiated, before the form's __init__ has executed, so it doesn't see the dynamic fields. That's why you have to override ModelAdmin.get_form.
I find out hot to solve dynamic MultipleChoiceField population with data, but still looking for proper solution of adding custom dynamic fields to ModelForm.
To populate MultipleChoiceField with existing values we need to pass choises on initialization:
forms.MultipleChoiceField(choices=choises)
To do that in dynamic way, we need to add our field to self.fields in __init__
self.fields['dynamic_field_name'] = forms.MultipleChoiceField(choices=choises)
To pass selected values:
self.initial['dynamic_field_name'] = initial_value
Complete code:
class GameAdminForm(forms.ModelForm):
dynamic_field = forms.MultipleChoiceField()
def __init__(self, *args, **kwargs):
super(GameAdminForm, self).__init__(*args, **
choises = Taxonomy.objects.all().values_list('id', 'name')
self.fields['dynamic_field'] = forms.MultipleChoiceField(choices=choises)
self.initial['dynamic_field'] = active_id_list
class Meta:
model = Game
fields = '__all__'
class GameAdmin(admin.ModelAdmin):
form = GameAdminForm
exclude = ['rating', '_id']
admin.site.register(Game, GameAdmin)
Related
I found a couple of questions regarding this, but I specifically wonder about how to add a field in the ModelForms __init__() method.
This is, because I get the number of fields from a function and need to display them in the admin:
class SomeForm(forms.ModelForm):
class Meta:
model = Product
fields = ["name", "price",]
def __init__(self, *args, **kwargs):
number_of_fields = get_number of fields(kwargs["instance"])
print(number_of_fields) ## e.g. 3, gives output
super().__init__(*args, **kwargs)
for i in range(number_of_fields):
self.fields[i] = forms.CharField("test", required = False)
But the fields do not show up in the Template Admin edit page. What did I miss? No error popping up either ...
Try something like this... but you need to pass field name into self.base_fields['name_of_the_field'] somehow
class SomeForm(forms.ModelForm):
class Meta:
model = Product
fields = ["name", "price",]
def __init__(self, *args, **kwargs):
number_of_fields = get_number_of_fields(kwargs["instance"])
print(number_of_fields) ## e.g. 3, gives output
for i in range(number_of_fields):
self.base_fields['name_of_the_field'] = forms.CharField(initial="test", required = False)
super(SomeForm, self).__init__(*args, **kwargs)
class Report(models.Model):
# ....
product = models.ForeignKey(Product)
class Product(models.Model):
name = models.CharField(max_length=50)
class Item(models.Model):
box = models.ForeignKey(BoxInTransport)
product = models.ForeignKey(Product)
class BoxInTransport(models.Model):
transport = models.ForeignKey(Transport)
box = models.ForeignKey(Box)
This is - in short - the structure of models.
And I have a view which lets me create new report:
class ReportCreateView(CreateView):
model = Report
form_class = ReportForm
def get_form_kwargs(self):
# updating to get argument from url
kwargs = super(DifferenceCreateView, self).get_form_kwargs()
kwargs.update(self.kwargs)
return kwargs
and the form:
class ReportForm(ModelForm):
class Meta:
model = Report
fields = [
'product'
]
def __init__(self, box_nr=None, *args, **kwargs):
super(ReportForm, self).__init__(*args, **kwargs)
self.fields['product'].queryset = ???
How can I get only these products which belong to a specific box? To be more clear:
Only products which:
Item.objects.filter(box__box__box_code=box_nr)
Now I get all Items which I need, but I need to pass self.fields['products'] to only product form with this new Items queryset.
Can you help me?
EDIT
I've tried something like this:
def __init__(self, box_nr=None, *args, **kwargs):
super(ReportForm, self).__init__(*args, **kwargs)
queryset = Item.objects.filter(
box__box__box_code=boxno
)
none_queryset = Product.objects.none()
list_or_products = [p.product for p in queryset]
product_queryset = list(chain(none_queryset, list_or_products))
self.fields['product'].queryset = product_queryset
But, first - it looks little ugly :), second - it doesn't work:
AttributeError: 'list' object has no attribute 'all'
Your __init__ could look something like this:
def __init__(self, box_nr=None, *args, **kwargs):
super(ReportForm, self).__init__(*args, **kwargs)
qs = Product.objects.filter(item__box__box__box_code=box_nr)
self.fields['product'].queryset = qs
Basically, you need a reverse lookup on Product to Item. You can read the relevant documentation here
Note that: item__box__box__box_code=box_nr is based on my understanding of your models. item__box does the reverse lookup. Rest might need some tweaking based on your model definitions.
I have form where I print all of records from table(lets say its 'item' table in database). User can add new items to db using ajax. Data saves to db correct but when I refresh page i don't see new tags in my multi select box.
I thought cache is a problem but it doesn't.
So I have question: Where is a problem that I can see add records correct but when i refresh this same page where every time select all rows from table then i don't see these new records?
I'm using sqlite and it's development server.
Forms.py:
BLANK_CHOICE = (('', '---------'),)
class OrderCreateForm(forms.ModelForm):
tag_from = forms.MultipleChoiceField(label='Tags', choices=OrderItemList.objects.all().values_list('id', 'name'))
tag_to = forms.MultipleChoiceField()
class Meta:
model = Order
fields = ('price', 'client', 'platform')
def __init__(self, request_client_id, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
self.fields['platform'].choices = BLANK_CHOICE + tuple(
Platform.objects.filter(client_id=request_client_id).values_list('id', 'name'))
View.py:
#user_passes_test(lambda u: u.is_staff, login_url='/account/login/')
def order_create(request, request_client_id):
dict = {}
dict['form_order'] = OrderCreateForm(request_client_id)
return render(request, 'panel/order/form.html', dict)
The problem is that you are setting the tag_from choices in the field definition, so the choices are evaluated once when the form first loads. You can fix the problem by setting the choices in the __init__ method instead.
class OrderCreateForm(forms.ModelForm):
tag_from = forms.MultipleChoiceField(label='Tags', choices=())
...
def __init__(self, request_client_id, *args, **kwargs):
super(OrderCreateForm, self).__init__(*args, **kwargs)
self.fields['tag_from'].choices = OrderItemList.objects.all().values_list('id', 'name'))
...
Another option would be to use a ModelMultipleChoiceField instead of a regular multiple choice field. With a model multiple choice field, Django will evaluate the queryset each time the form is initialised.
class OrderCreateForm(forms.ModelForm):
tag_from = forms.MultipleChoiceField(label='Tags', queryset=OrderItemList.objects.all())
I'm trying to override concept queryset in my child form, to get a custom list concepts based on the area got from request.POST, here is my list of concepts, which i need to filter based on the POST request, this lists is a fk of my child form (InvoiceDetail). is it possible to have these filters?
after doing some test when I pass the initial data as the documentation says initial=['concept'=queryset_as_dict], it always returns all the concepts, but i print the same in the view and its ok the filter, but is not ok when i render in template, so I was reading that I need to use some BaseInlineFormset. so when I test I obtained different errors:
django.core.exceptions.ValidationError: ['ManagementForm data is missing or has been tampered with']
'InvoiceDetailFormFormSet' object has no attribute 'fields'
so here is my code:
models.py
class ConceptDetail(CreateUpdateMixin): # here, is custom list if area='default' only returns 10 rows.
name = models.CharField(max_length=150)
area = models.ForeignKey('procedure.Area')
class Invoice(ClusterableModel, CreateUpdateMixin): # parentForm
invoice = models.SlugField(max_length=15)
class InvoiceDetail(CreateUpdateMixin): # childForm
tax = models.FloatField()
concept = models.ForeignKey(ConceptDetail, null=True, blank=True) # fk to override using custom queryset
invoice = models.ForeignKey('Invoice', null=True, blank=True)
views.py
class CreateInvoiceProcedureView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
template_name = 'invoice/invoice_form.html'
model = Invoice
permission_required = 'invoice.can_check_invoice'
def post(self, request, *args, **kwargs):
self.object = None
form = InvoiceForm(request=request)
# initial initial=[{'tax': 16, }] removed
invoice_detail_form = InvoiceDetailFormSet(request.POST, instance=Invoice,
request=request)
return self.render_to_response(
self.get_context_data(
form=form,
invoice_detail_form=invoice_detail_form
)
)
forms.py
class BaseFormSetInvoice(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
# call first to retrieve kwargs values, when the class is instantiated
self.request = kwargs.pop("request")
super(BaseFormSetInvoice, self).__init__(*args, **kwargs)
self.queryset.concept = ConceptDetail.objects.filter(
Q(area__name=self.request.POST.get('area')) | Q(area__name='default')
)
class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
fields = ('invoice',)
class InvoiceDetailForm(forms.ModelForm):
class Meta:
model = InvoiceDetail
fields = ('concept',)
InvoiceDetailFormSet = inlineformset_factory(Invoice, InvoiceDetail,
formset=BaseFormSetInvoice,
form=InvoiceDetailForm,
extra=1)
How can i fix it?, what do i need to read to solve this problem, I tried to debug the process, i didn't find answers.
i try to do this:
def FooForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
self.fields['concept'].queryset = ConceptDetail.objects.filter(area__name='default')
In a inlineformset_factory how can do it?.
After a lot of tests, my solution is override the formset before to rendering, using get_context_data.
def get_context_data(self, **kwargs):
context = super(CreateInvoiceProcedureView, self).get_context_data(**kwargs)
for form in context['invoice_detail_form']:
form.fields['concept'].queryset = ConceptDetail.objects.filter(area__name=self.request.POST.get('area'))
return context
I'm using the Django Form View and I want to enter custom choices per user to my Choicefield.
How can I do this?
Can I use maybe the get_initial function?
Can I overwrite the field?
When I want to change certain things about a form such as the label text, adding required fields or filtering a list of choices etc. I follow a pattern where I use a ModelForm and add a few utility methods to it which contain my overriding code (this helps keep __init__ tidy). These methods are then called from __init__ to override the defaults.
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
self.set_querysets()
self.set_labels()
self.set_required_values()
self.set_initial_values()
def set_querysets(self):
"""Filter ChoiceFields here."""
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
def set_labels(self):
"""Override field labels here."""
pass
def set_required_values(self):
"""Make specific fields mandatory here."""
pass
def set_initial_values(self):
"""Set initial field values here."""
pass
If the ChoiceField is the only thing you're going to be customising, this is all you need:
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = ('country', 'contact_phone', )
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
# only show active countries in the ‘country’ choices list
self.fields["country"].queryset = Country.objects.filter(active=True)
You can then make your FormView use this form with like this:
class ProfileFormView(FormView):
template_name = "profile.html"
form_class = ProfileForm