Django modelformset order_by not working - python

I want to use a Django (1.4) modelformset in which, when the formset is loaded the forms will be arranged by a exam_date field I have in my model. To do this I've created the following simple BaseModelFormSet
class BaseExamDateFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseExamDateFormSet, self).__init__(*args, **kwargs)
self.queryset = models.ExamDate.objects.all().order_by('exam_date')
As you can see I've changed the queryset as proposed in the
django docs (later I'll also change its clean method but it doesn't matter right now). After that I am using it in my view to create the formset:
ExamDateFormSet = modelformset_factory(
models.ExamDate,
exclude =('authority', ),
can_delete=True,
extra=1,
formset = forms.BaseExamDateFormSet
)
...
formset = ExamDateFormSet()
My problem is that when the formset is rendered the data in the forms is always in the same ordering (probaby by id) regardless of the value of the order_by attribute :(
Should I try setting a default order by in my ExamDate model ? I really don't like that solution though :(
TIA !

After some more searching I found a solution !
I ignored the queryset attribute and added the following method to the BaseExamDateFormSet:
def get_queryset(self):
return models.ExamDate.objects.all().order_by('exam_date')
However I don't know yet why the queryset attribute isn't working.

Easier still, in your above code you could erase your formset= argument in modelformset_factory call and instantiate your formset as:
formset = ExamDateFormSet(queryset=models.ExamDate.objects.order_by('exam_date'))

Related

Django contect_object_name is not producing a context dict in view

I am back with more django questions on CBVs. This is about context_object_name. I have the following:
#method_decorator(verified_email_required, name='dispatch')
class Create(CreateView):
model = Profile
context_object_name = 'profileForm'
template_name = 'Members/template_includes/profile/form.html'
form_class = ProfileForm
success_url = '/Members'
form_title = "New Login Profile Information"
def get(self, request, *args, **kwargs):
return render(request, self.template_name, {
'profileTitle': self.form_title,
})
I am using PyCharm and can put a breakpoint in the template_name form and see what the environment knows about. I expect to see a dict named profileForm with all the form members in it plus profileTitle. Instead I see profileTitle as a standalone member. I do not see anything named profileForm or object_list and the expected form members are not being painted in the template.
I suppose that I understand that the extra content in the return render will pass a "naked" profileTitle but I did expect that the default get behaviour would pull in the form info.
Have I missed the point?
You've overridden the get method in your CreateView-subclass and in doing so, you've bypassed the included functionality that a CreateView does to fill your context. If you take a look here you can see that a CreateView would otherwise call return self.render_to_response(self.get_context_data()) (because it inherits from ProcessFormView) and it's within get_context_data() (ref) that those included context variables are set up.

MutipleCheckBox saving to Database with Django

I'm trying to find some kind of canonical reference for this problem where I use the follow type model:
class MyPreferencesForm(forms.Form):
MyGenderPref = forms.MultipleChoiceField(choices=GENDER_CHOICES, widget=forms.CheckboxSelectMultiple())
and the view
def GoPreferences(request):
if request.method == "POST":
f = MyPreferencesForm(request.POST)
if f.is_valid():
model_instance = f.save(commit=False)
model_instance.save()
f.save_m2m()
return redirect('/')
else:
f = MyPreferencesForm()
return render(request, "mypreferences.html", {'form': f})
However I tried to follow what I thought was sensible and include the many to many. But I still cannot make this work. My error is the following:
Exception Type: AttributeError
Exception Value:
'MyPreferencesForm' object has no attribute 'save'
The error emanates from line containing model_instance = f.save(commit=False)
save is available only for forms inherited from ModelForm objects. Here, you are inheriting form forms.Form Hence the error.
Either inherit the form from forms.ModelForm and specify a Meta attribute
class MyPreferencesForm(forms.ModelForm):
MyGenderPref = forms.MultipleChoiceField(choices=GENDER_CHOICES, widget=forms.CheckboxSelectMultiple())
class Meta:
model = MyModel
You can read more on ModelForms here - You might have to modify your form slightly based on your specific requirements.
OR
model_instance = MyModel.objects.create(**form.cleaned_data) #manage manually
If your m2m tables in your models.py uses through then you'll need to manage the saving manually using object.create and you can't use save_m2m() or save()

Populate formset with information from db

I have a view where the user should be able to update an instance of a model, and also update or create new instances of a model related to the first one. I tryied using formsets to do this, and it works perfeclty for creating new objects, but i'm not finding a way to show the objects that already have been created. My problem is that i don't know how to populate the formsets with the existing data, so that i can put it in the context
So this are my models:
class Order(Model):
...
class invoice(Model):
order = models.ForeignKey(Order)
...
And my view is something like this:
class OrderDetailView(UpdateView):
invoice_form_class = InvoiceForm
def get_context_data(self, **kwargs):
context = super(OrderDetailView, self).get_context_data(**kwargs)
if not 'invoice_formset' in context:
context['invoice_formset'] = formset_factory(self.invoice_form_class, extra=3, can_delete=True, formset=BaseFormSet)
return context
There's probably an easy way to do this, but i'm not finding it anywhere
EDIT:
Thanks to #mariodev, i've learned about the inline_formsetfactory, and i'm using it. Now i can fill the formsets with the existing data, and i can create and alter existing ones, but when i try to delete them, nothing happens.
So now i'm defining this formset:
InvoiceFormset = inlineformset_factory(Order, Invoice, fields=('code',), can_delete=True, extra=0)
and my view looks like:
class OrderDetailView(UpdateView):
invoice_form_class = InvoiceForm
def get_context_data(self, **kwargs):
context = super(OrderDetailView, self).get_context_data(**kwargs)
if not 'invoice_formset' in context:
context['invoice_formset'] = InvoiceFormset(instance=self.get_object())
return context
def post(self, *args, **kwargs):
data = self.request.POST
order = self.get_object()
form = self.form_class(data)
invoice_formset = InvoiceFormset(data, instance=order)
if form.is_valid() and invoice_formset.is_valid():
self.object = form.save(order)
for f in invoice_formset:
f.save(self.object)
return HttpResponseRedirect(reverse('order_detail', kwargs={'order_id': self.get_object().order_id}))
I could add some extra lines in the post() to check if i have to delete the form, but it doesn't seem right for me to do it in the view. Is there something else i'm missing?
EDIT AGAIN:
Ended up finding this link which fix exactly this last problem i was having, so now it's all good!
I think it's better use normal function based views (FBV) for this. Understand what's going on first and then gradually move to CBV if you really need to.
This will help you with FBV:
http://catherinetenajeros.blogspot.com/2013/03/inline-formset-saving-and-updating-two.html
This may help you with CBV:
django class-based views with inline model-form or formset

Django - Passing parameters to inline formset

I am using inlineformset_factory to create fields for a many to many relationship between Clients and Sessions, with an intermediary Attendance model.
I have the following in my views file:
AttendanceFormset = inlineformset_factory(
Session,
Attendance,
formset=BaseAttendanceFormSet,
exclude=('user'),
extra=1,
max_num=10,
)
session = Session(user=request.user)
formset = AttendanceFormset(request.POST, instance=session)
And, as I needed to override one of the form fields, I added the following to the formset base class:
class BaseAttendanceFormSet(BaseFormSet):
def add_fields(self, form, index):
super(BaseAttendanceFormSet, self).add_fields(form, index)
form.fields['client'] = forms.ModelChoiceField(
queryset=Client.objects.filter(user=2))
Now, the form works correctly, but I need to pass a value into the formset so that I can filter the clients displayed based the current user rather than just using the id 2.
Can anyone help?
Any advice appreciated.
Thanks.
EDIT
For anyone reading, this is what worked for me:
def get_field_qs(field, **kwargs):
if field.name == 'client':
return forms.ModelChoiceField(queryset=Client.objects.filter(user=request.user))
return field.formfield(**kwargs)
How about utilizing the inlineformset_factory's formfield_callback param instead of providing a formset ? Provide a callable which in turns returns the field which should be used in the form.
Form fields callback gets as 1st parameter the field, and **kwargs for optional params (e.g: widget).
For example (using request.user for the filter, replace with another if needed:
def my_view(request):
#some setup code here
def get_field_qs(field, **kwargs):
formfield = field.formfield(**kwargs)
if field.name == 'client':
formfield.queryset = formfield.queryset.filter(user=request.user)
return formfield
AttendanceFormset = inlineformset_factory(
...
formfield_callback=get_field_qs
...
)
formset = AttendanceFormset(request.POST, instance=session)
To better understand it, see the usage of formfield_callback in Django's FormSet code.

Django: How to set initial values for a field in an inline model formset?

I have what I think should be a simple problem. I have an inline model formset, and I'd like to make a select field have a default selected value of the currently logged in user. In the view, I'm using Django's Authentication middleware, so getting the user is a simple matter of accessing request.user.
What I haven't been able to figure out, though, is how to set that user as the default selected value in a select box (ModelChoiceField) containing a list of users. Can anyone help me with this?
This does the trick. It works by setting the initial values of all "extra" forms.
formset = MyFormset(instance=myinstance)
user = request.user
for form in formset.forms:
if 'user' not in form.initial:
form.initial['user'] = user.pk
I'm not sure how to handle this in inline formsets, but the following approach will work for normal Forms and ModelForms:
You can't set this as part of the model definition, but you can set it during the form initialization:
def __init__(self, logged_in_user, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
self.fields['my_user_field'].initial = logged_in_user
...
form = MyForm(request.user)
I'm using Rune Kaagaard's idea above, except I noticed that formsets provide an extra_forms property: django.forms.formsets code
#property
def extra_forms(self):
"""Return a list of all the extra forms in this formset."""
return self.forms[self.initial_form_count():]
So, sticking with the example above:
formset = MyFormset(instance=myinstance)
user = request.user
for form in formset.extra_forms:
form.initial['user'] = user.pk
Saves having to test any initial forms, just provide default for extra forms.

Categories