I am trying to figure out how to dynamically change a ModelForm field based on the input from a previous field.
For example, if I have these kinds of models:
class Phone(models.Model):
name = models.CharField(max_length=10)
class Series(models.Model):
name = models.CharField(max_length=10)
class Manufacturer(models.Model):
phone = models.ForeignKey('Phone')
series = models.ForeignKey('Series')
class ManufacturerForm(ModelForm):
class Meta:
model = Manufacturer
Which would generate a form (ManufacturerForm) with dropdown options for the phone and series entries in the database. Is it possible to a different set of series entries based on the phone entered by the user, for example?
I have read about using the __init__ method to accomplish this, based on what I have read on this blog post, but I am not sure how to execute this given my scenario. Or maybe there is a better way to go about achieving this that you have taken? Thanks for any advice!
EDIT: Added the form's view.
def make_ad(request):
if request.method == 'POST':
form = ManufacturerForm(request.POST, request.FILES)
if form.is_valid():
a = form.save()
a.user = request.user
a.save()
else:
form = ManufacturerForm()
variables = RequestContext(request, {
'form': form
})
return render_to_response('place.html', variables)
#super9 mentioned using ajax request to change these elements. I need to check if request.is_ajax(), but at what point should I check this in my view? And how do I add or change the queryset based on the ajax request?
Thanks for your advice.
EDIT: Trying to use django-smart-selects
Not sure how to setup my models to utilize django-smart-selects to accomplish what I am trying. Here is how I have structured my models:
from smart_selects.db_fields import ChainedForeignKey
class Phone(models.Model):
phone = models.CharField(max_length=10)
class Series(models.Model):
series = models.CharField(max_length=10)
phone = models.ForeignKey(Phone)
class SeriesModel(models.Model):
model = models.CharField(max_length=10)
series = models.ForeignKey(Series)
class Manufacturer(models.Model):
phone = models.ForeignKey(Phone)
series = ChainedForeignKey (Series, chained_field = "phone", chained_model_field = "phone")
series_model = ChainedForeignKey (SeriesModel, chained_field = "series", chained_model_field = "series")
But when I view my form (ModelForm) the fields for series_model are not chained properly to series. Am I missing something to make smart-selects work on the second layer of abstraction?
EDIT: Above code now works.
Related
I am able to prefill a form using query-string parameters in Django Admin.
Let's say I have the following models:
class Book(models.Model):
id = models.Autofield(primary_key=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
name = models.Charfield(max_length=200)
class Author(models.Model):
id = models.Autofield(primary_key=True)
name = models.Charfield(max_length=200)
If I go to /admin/library/author/add/?name=J.+K.+Rowling the author's name will be properly prefilled.
However if I add InlineForms like that:
class BookInline(StackedInline):
model = Book
extra = 0
class AuthorAdmin(ModelAdmin):
inlines = [BookInline]
admin.site.register(Author, AuthorAdmin)
I don't seem to be able to prefill books.
I tried: /admin/library/author/add/?name=J.+K.+Rowling&books-TOTAL_FORMS=1&books-0-name=Harry+Potter+and+the+Philosopher's+Stone
The author form is prefilled, but the first book form is not prefilled. Do you know how one manages that?
If you override get_formset_kwargs, you can prefill forms with some initial values:
class AuthorAdmin(ModelAdmin):
inlines = [BookInline]
def get_formset_kwargs(self, request, obj, inline, prefix):
formset_params = super().get_formset_kwargs(request, obj, inline, prefix)
if request.method == "GET":
# Mind you, this will prefill all form of the formset with the same values.
# But for our usecase it is sufficient.
prefix_length = len(prefix) + 3
initial_values = [{key[prefix_length:]: value for key, value in request.GET.items() if key.startswith(prefix)}]
formset_params.update(initial=initial_values)
return formset_params
However, all extra form will contain the same values.
If you know how we can manage to use the index there, it would improve this answer.
I'm trying to accomplish a three-level stacked inline form in Django. Suppose these models:
class Anuncio(models.Model):
title = models.CharField(max_length=200)
delivery = models.CharField(max_length=100)
class Product(models.Model):
anuncio = models.ForeignKey(Anuncio, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
quantity = models.PositiveIntegerField(default=1)
price = models.PositiveIntegerField()
class Image(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
image = models.ImageField()
There is a relation Anuncio-Product and another relation Product-Image. With this Django package, I accomplished exactly what I want in the Django admin: when creating an Anuncio object, I can add as many Products as I want, and those products can have as many Images as I want. I'm trying to accomplish this in the front end.
I think the way to go is with Django formsets, but I'm facing some problems. All the resources I've been able to find online are only 'two-level' formsets or in 'three-level' cases all the foreign keys point to the same parent model.
With this forms.py file:
class ProductForm(ModelForm):
class Meta:
model = Product
fields = ['name', 'quantity', 'price']
class ImageForm(ModelForm):
class Meta:
model = Imagen
fields = ['image']
class AnuncioForm(ModelForm):
class Meta:
model = Anuncio
fields = ['title', 'delivery']
And this views.py function:
def anunciocreateview(request):
form = AnuncioForm(request.POST or None)
ProductFormSet = inlineformset_factory(Anuncio, Product, form=ProductForm)
ImageFormSet = inlineformset_factory(Product, Image, form=ImageForm)
if all([form.is_valid(), ProductFormSet.is_valid(), ImageFormSet.is_valid()]):
parent = form.save(commit=False)
parent.anunciante = request.user
parent.save()
for form in ProductoFormSet:
child = form.save(commit=False)
child.anuncio = parent
child.save()
for form in ImagenFormSet:
imagen = form.save(commit=False)
imagen.product = form.product
imagen.save()
context = {
'form_1' : form,
'form_2' : ProductFormSet,
'form_3' : ImageFormSet,
}
But I think I'm missing important points when it comes to add the proper relations between models. This set-up gives an AttributeError of: 'ProductForm' object has no attribute '__name__'
The, for example, 'add (extra) Product' that appears in AdminStackedInLine I guess it can be accomplished with JavaScript, playing with hidden forms and changing attributes on click events.
Anyone has experience doing something similar or can guide me through the correct direction? Also on how to manage the data and the relations of the submitted forms?
I think your problem is you have tried to validate a class Form instead of instanciate your formset and validate them.
Your code would be look like to something like that :
def anunciocreateview(request):
ProductFormSet = inlineformset_factory(Anuncio, Product, form=ProductForm)
ImageFormSet = inlineformset_factory(Product, Image, form=ImageForm)
anuncio_form = AnuncioForm(request.POST or None)
product_formset = ProductFormSet(request.POST or None)
image_formset = ImageFormSet(request.POST or None)
if all([form.is_valid(), product_formset.is_valid(), image_formset.is_valid()]):
...
The function inlineformset_factory just create a Form class, not a instance of form.
More information and example on the documentation : https://docs.djangoproject.com/fr/4.1/topics/forms/formsets/
Is there an easy way to select the first option of a dropdown in a Django form, in cases where there is only one option available?
With easy I mean a solution that doesn't require defining an own widget, which would be quite repetitive to change in all our (model) forms.
Update because it seems like the initial question was unclear:
I want the initital option only to be selected if there is one option available. And the the way to do that is non-obvious to me, if the options are foreign key references to another model:
class Category(models.Model):
name = CharField(...)
class Post(models.Model):
category = ForeignKey(Category)
class PostForm(forms.ModelForm):
class Meta:
fields = '__all__'
[...]
Now I want the category field in the PostForm to be autoselected to the first category, if only one instance is present in the database and be -------- if there a two or more categories
What about using this :
Your_Dropdown = forms.TypedChoiceField(choices=your choises, initial='FIRST_OPTION')
As shown in documentation: Here
I don't think any explanation is required, have a look at following code
post_type_choices = (
('article', 'article'),
('tip', 'tip'),
('snippet', 'snippet'),
)
class Post(models.Model):
post_type = models.CharField(
max_length=10, choices=post_type_choices,default='article')
I did it in the get_form() like this:
def get_form(self, form_class=form_class):
form = super(WPWeekSubjectCreateView, self).get_form(form_class)
....
subjects = Subject.objects.filter(
schoolyear=schoolyear,
pk__in=subject_bearings,
lesson__teachers=teacher
).order_by('abbreviation').distinct()
form.fields['subject'].queryset = subjects
if subjects.count() == 1:
form.fields['subject'].initial = subjects.first()
return form
I am building a filter for my website where people can filter by cuisine. In order to achieve this I used a model form to receive input information that sets the filter variable in a query in my view. However as you can see in the image linked below, the default select for my cuisine categories is '-------' .
How would I go about changing this to say the words 'all' and setting a value so my filter queries everything for those categories? I think it has something to do with using a form method but I have been unable to understand what is actually happening in some of the examples.
Here is my simple code
Models
class Cuisine(models.Model):
name = models.CharField()
def __str__(self):
return self.name
class Food(models.Model):
name = models.CharField()
cuisine = models.ForeignKey(Cuisine)
def __str__(self):
return self.name
Views
def home_page(request):
if request.method == 'GET':
form = FilterForm(request.GET)
if form.is_valid():
cuisine = form.cleaned_data['cuisine']
food = get_list_or_404(Food, cuisine__pk=cuisine.pk)
return render('base.html', {'food': food, 'form':form})
else:
form = FilterForm()
return render('base.html', {'form':form})
Form
class FilterForm(forms.ModelForm):
class Meta:
model = Cuisine
fields = ('name')
I wouldn't use a modelform here. You only have one field, and you're not using it to create or edit instances of Food or Cuisine. It would be simpler to use a manual form with a ModelChoiceField, to which you can pass the empty_label parameter.
class FilterForm(forms.Form):
cuisine = forms.ModelChoiceField(queryset=Cuisine.objects.all(),
empty_label="All")
(Note you could do this with the ModelForm as well, but that just makes it even more pointless, as you are now not using any of the ModelForm functionality.)
I have a model Calendar and in a form I want to be able to create multiple instances of it.
Here are my models:
class Event(models.Model):
user = models.ForeignKey(User)
class Group(models.Model):
name = models.CharField(_('Name'), max_length=80)
events = models.ManyToManyField(Event, through='Calendar')
class Calendar(models.Model):
event = models.ForeignKey(Event)
group = models.ForeignKey(Group)
class CalendarInline(admin.TabularInline):
model = Calendar
extra = 1
class GroupAdmin(admin.ModelAdmin):
inlines = (CalendarInline,)
Here is how I try to code my form:
class AddEventToGroupForm(ModelForm):
group = ModelMultipleChoiceField(queryset=Group.objects.all(), widget=SelectMultiple())
def save(self):
for g in self:
g.save()
class Meta:
model = Calendar
fields = ('group',)
And here is a part of my view:
e = Event.objects.get(id=event_id)
calentry = Calendar(event=e)
if request.POST:
f = AddEventToGroupForm(data=request.POST, instance=calentry)
if f.is_valid():
f.save()
If I try to submit that form, I get:
AttributeError at /groups/add_event/7/
'BoundField' object has no attribute 'save'
What is the proper way to create multiple instances of Calendar in this
situation?
That's not how to deal with many-to-many relationships in forms. You can't iterate through fields in a form and save them, it really doesn't work that way.
In this form, there's only one field, which happens to have multiple values. The thing to do here is to iterate through the values of this field, which you'll find in the cleaned_data dictionary (when the form is valid).
So, in your view, you do something like:
if f.is_valid():
for group in f.cleaned_data['group']:
calentry.groups.add(group)
Note you're not 'saving' the AddEventToGroupForm form at all. I would make it a standard forms.Form, rather than a ModelForm, as you're not really depending on any of the ModelForm functionality.