Django modelformset with customized model initialization - python

I have the following models in django:
class tag(models.Model):
...
user = models.ForeignKey(user)
shop = models.ForeignKey(shop)
name = models.CharField()
...
class user(models.Model):
...
name = models.CharField()
...
class shop(models.Model):
...
user = models.ForeignKey(user)
name = models.CharField()
...
Now I need to create a modelformset of tag model that will allow me to store bunch of different tags per user. What I am looking for is a way to initialize such formset with a shop field filtered out for a given user.
This way my forms would have a selection of shops that are assigned to the user.
Any ideas are very welcome!
Thanks

You can use inlineformset_factory to create a formset for related models. To create a formset for your tag and user model you can do the following:
tag_formset = inlineformset_factory(user, tag, fields=['shop', 'name'])
formset = tag_formset(instance=request.user)
To filter the queryset for the shop field you can either create a form dynamically or pass formfield_callback to the factory function.
If you use formfield_callback, this callback can be used to return a custom field for your form
def cb(field, **kwargs):
if field.name == 'shop':
kwargs['queryset'] = shop.objects.filter(user=request.user)
return field.formfield(**kwargs)
tag_formset = inlineformset_factory(user, tag, fields=['shop', 'name'], formfield_callback=cb)
formset = tag_formset(instance=request.user)
Or you can define a form class dynamically in your view
class TagForm(forms.ModelForm):
shop = forms.ModelChoiceField(shop.objects.filter(user=request.user))
class Meta:
fields = ['shop', 'name']
tag_formset = forms.inlineformset_factory(user, tag, form=TagForm)
formset = tag_formset(instance=request.user)

Related

Django Admin - How to prefill add inline forms values sent through the querystring?

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.

Three-level inline formsets in the frontend

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/

filter a field in a dropdown lit in django admin

i have a very simple django model:
class Car(models.Model):
carname = models.CharField(max_length=100)
carmodel = models.CharField(max_length=100)
carcountry = models.CharField(max_length=100)
caryear = models.CharField(max_length=100)
cardesc = models.TextField()
and a admin.py that shows the all records:
class UserForm(forms.ModelForm):
cardesc = forms.CharField(widget=forms.Textarea(attrs={'cols': 120, 'rows': 20}))
class Meta:
model = Cars
class ModelAdmin(admin.ModelAdmin ):
form = UserForm
admin.site.register(Cars,ModelAdmin)
it works fine. now, my question is that:
i want to have a drop down list and i can select car model and then my results filtered based my choice...
how i can do that? i have to edit Admin.py file? Django has a builtin feature for this? or i have to create an HTML template? how? please help me.
Try this .
class CustomForm(forms.ModelForm):
class Meta:
model = Cars
cars_list = Cars.objects.values_list('carmodel', flat=True).order_by('carmodel').distinct()
choice_list = []
for car in cars_list:
choice_list.append((car,car,))
CAR_CHOICES = choice_list
#Used 'ChoiceField' as you want dropdown list for carmodels stored in text fields
carmodel = forms.ChoiceField(widget=forms.Select, choices=CAR_CHOICES)
class CarsModelAdmin(model.ModelAdmin):
form = CustomForm
list_filter = ['carmodel']
admin.site.register(Cars,CarsModelAdmin)
You would have to add
list_filter = ['carmodel', ]
to class ModelAdmin.
From the documentation:
ModelAdmin.formfield_for_foreignkey(db_field, request, **kwargs)¶
The formfield_for_foreignkey method on a ModelAdmin allows you to override the default formfield for a foreign keys field. For example, to return a subset of objects for this foreign key field based on the user:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This uses the HttpRequest instance to filter the Car foreign key field to only display the cars owned by the User instance.

Copying over the fields from a ModelForm into a Form

I have a dummy Form that looks like:
class MyForm(forms.Form):
class __init__(self, *args, **kwargs):
pass
Here's are the ModelForms and their respective Models:
class Person(models.Model):
fname = models.CharField(max_length = 255)
lname = models.CharField(max_length = 255)
class Address(models.Model):
address = models.CharField(max_length = 255)
person = models.ForeignKey(Person)
class PersonForm(ModelForm):
class Meta:
model = Person
class AddressForm(ModelForm):
class Meta:
model = Address
I would like to initiate MyForm like this myfrm = MyForm(PersonForm, AddressForm).
How can I dynamically add the fields of each of the Forms to MyForm excluding AutoFields and ForeignKey fields i.e. MyForm when initiated would have three fields in the end — fname, lname and address. It's a simple copying of the fields from one form to another but I'm a little lost with it.
Thanks
Just adding a field is not correct, considering the models above.
Address has a ManyToOne relation to Person - A user can have multiple addresses. In that case better take a look at django's inline form sets:
https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view
Cumbersome, but You can also manually add a CharField to to Person's ModelForm, override it's save and handle saving the content of that field to Address (ugly, I don't like it).
You can put both forms inside one html <form> tag, using the prefix argument. You then do a bit of work in the view to link the address to the person.
def my_view(request):
if request.method == "POST":
person_form = PersonForm(data=request.POST, prefix="person")
address_form = AddressForm(data=request.POST, prefix="address")
if person_form.is_valid() and address_form.is_valid():
person = person.save()
address = address.save(commit=False)
address.person = person
address.save()
return HttpResponseRedirect('/success-url/')
...

how can I save a form with ModelMultipleChoiceField?

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.

Categories