Just going to put some code and explain at the bottom.
# modelforms.py
#
class MyModelModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(MyModelModelForm, self).__init__(*args, **kwargs)
print '__init__'
class Meta:
model = MyModel
exclude = ('my_fk', )
Here I am using django-vanilla-views
# views.py
#
class MyCreateView(NoAdminNoStaffLoginRequiredMixin, HasPermissionsMixin, CreateView):
template_name = 'modelform_create.html'
form_class = MyOtherModelModelForm # this modelform isn't important
required_permission = 'create_model'
def get_success_url(self):
return reverse_lazy('mymodel_detail', kwargs={'pk': self.object.pk})
def get_context_data(self, **kwargs):
context = super(MyCreateView, self).get_context_data(**kwargs)
# FIXME: form's __init__ being called a lot
MyModelFormset = modelformset_factory(
MyModel, form=MyModelModelForm, extra=4, max_num=10, validate_max=True)
if self.request.POST:
formset = MyModelFormset(
self.request.POST,
queryset=MyModel.objects.none(),
form_kwargs={'request': self.request})
else:
formset = MyModelFormset(
queryset=MyModel.objects.none(),
form_kwargs={'request': self.request})
print formset.total_form_count() # 4, which makes sense
context['formset'] = formset
return context
MyModelModelForm __init__ gets called 18 times. Yeah, I was wondering if I could get that from not happening.
Reason being I would like it to not be called multiple times is I query the DB for forms.ChoiceField(choices=...), which queries the DB that many times unnecessarily. Or if there is a better spot for populating choices (NOTE: I cannot just do it in the same space where the field is declared.) It is a mixin that populates this certain field.
If any more info is needed let me know. I had to trim down the code for only relevant info.
Related
So I've a formset tied to a model and one of the fields in that is ForeignKey.
models.py
class Squad(models.Model):
rid = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE)
def __str__(self):
return self.team.tname
forms.py
class SquadForm(ModelForm):
class Meta:
model = Squad
def __init__(self, logged_user, user, *args, **kwargs):
super(SquadForm, self).__init__(*args, **kwargs)
self.fields['team'] = forms.ModelChoiceField(queryset=Team.rows.get_my_teams(user=logged_user), empty_label="None")
As you can see, the __init__ function is expecting an extra parameter logged_user which I'm hoping to pass via the views.py file. But if I do the following:
views.py
def choose_teams(request):
teamformset = modelformset_factory(Squad, extra=2, form=SquadForm(request.user))
form = teamformset(queryset=Squad.objects.none())
return render(request, 'foo.html', {'form':form})
I'm trying to pass the logged in user as a parameter on line 2 but this is resulting in the following message:
Field 'id' expected a number but got 'SquadForm'
Not sure what I'm missing here. But if I remove the parameter from line 2:
teamformset = modelformset_factory(Squad, extra=series.team_number, form=SquadForm)
it starts working (of course, I no longer expect the user in the forms.py file and remove it too) but shows all the data and not filtered one.
You can pass additional keyword arguments to your formset form by passing form_kwargs={} to your formset
class SquadForm(ModelForm):
class Meta:
model = Squad
def __init__(self, *args, logged_user, **kwargs):
super(SquadForm, self).__init__(*args, **kwargs)
self.fields['team'] = forms.ModelChoiceField(queryset=Team.rows.get_my_teams(user=logged_user), empty_label="None")
teamformset = modelformset_factory(Squad, extra=2, form=SquadForm)
form = teamformset(queryset=Squad.objects.none(), form_kwargs={'logged_user': request.user})
Can Anyone please explain me what this error means??
I have done this in my views.py:
class FormListView(FormMixin, ListView):
def get(self, request, *args, **kwargs):
# From ProcessFormMixin
form_class = self.get_form_class()
self.form = self.get_form(form_class)
# From BaseListView
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty and len(self.object_list) == 0:
raise Http404(_(u"Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
context = self.get_context_data(object_list=self.object_list, form=self.form)
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
class CompanyListView(LoginRequiredMixin,FormListView):
model = Company
form_class = daterangeform
paginate_by = 10
def get_queryset(self):
return company.objects.filter(User=self.request.user)
def get_context_data(self, **kwargs):
context = super(companyListView, self).get_context_data(**kwargs)
context['selectdate_list'] = selectdate.objects.filter(User=self.request.user).latest()
return context
And I am getting this error:
ValueError: earliest() and latest() require either fields as positional arguments or 'get_latest_by' in the model's Meta.
Can anyone please explain me what is wrong in my code and possible solution for doing it in correct way...
Thank you
As specified in the documentation for latest(*fields) [Django-doc]:
Returns the latest object in the table based on the given
field(s).
This example returns the latest Entry in the table, according to the
pub_date field:
Entry.objects.latest('pub_date')
So if you want to obtain the latest object with respect to a field (for example updated_date), you can write this as:
selectdate.objects.filter(
User=self.request.user
).latest('updated_date')
You can however use the latest() without parameters, given you specified the order for this in the Meta class of the model, like:
class Foo(models.Model):
name = models.CharField(max_length=20)
class Meta:
get_latest_by = ['name']
In that case
Foo.objects.latest()
will give the Foo object with the maximum name (if we here compare the names lexicographically).
I have an issue with my Create view. I initialise it like this:
class OutputCreateView(LoginRequiredMixin, generic.CreateView):
template_name = 'rcapp/common_create_update.html'
form_class = OutputForm
model = Output
def get_context_data(self, **kwargs):
context = super(OutputCreateView, self).get_context_data(**kwargs)
# self.form_class.fields['activity_ref'].queryset = Activity.objects.filter(rc_ref=ResultsChain.objects.get(pk=self.kwargs['rc']).pk)
context['is_authenticated'] = self.request.user.is_authenticated
return context
def form_valid(self, form):
# code code code
return redirect("/portal/edit/" + str(self.kwargs['rc']) + "/#outputs-table")
I have a ForeignKey Field in my model and I wanted to filter options for current view.
My form is set like this:
class OutputForm(forms.ModelForm):
class Meta:
model = Output
fields = ['value', 'activity_ref']
widgets = {
'value': forms.Select(choices=(#Choises here
,), attrs={"onChange":'select_changed()', 'class':'selector'})
}
I need to change a queryset for the activity_ref field.
You can see a commented line in get_context_data, it's where I tried to do this. But it didn't work. How can I get what I need?
You need to pass the choices / queryset to your form.
in OutputCreateView
def get_form_kwargs(self, *args, **kwargs)
filter_key = self.kwargs['rc']).pk
return {'filter_key': key}
Like this, it will give an error in when your form gets created, because of the unexpected argument. To get around that and to make use of it, override the init method.
In your OutputForm
def __init__(self, *args, **kwargs)
kwargs.pop('filter_key')
super()._init__(*args, **kwargs)
self.fields['value'] = forms.Select(queryset=Activity.objects.filter(rc_ref=ResultsChain.objects.get(pk=self.kwargs['rc']).pk),
attrs={"onChange":'select_changed()', 'class':'selector'})
You don't need to set the widgets value, as it is being done in the init method.
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
profile = get_object_or_404(Profile, user__username=self.request.user)
context['profile'] = profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
profile = self.get_context_data()['profile']
return super(CourseListView, self).get_queryset().filter(creator=profile)
I have the following two class-based-views. CourseListView inherits ProfileContextMixin which I wrote so that I don't have to repeat overriding get_context_data to get the profile every time in my other views.
Now in my CourseListView, I need to filter the result based on the creator argument, which is the same one retrieved in get_context_data
I know my get_queryset works, and it will call get_context_data() to get the profile, but this will also cause my get_context_data to be called twice, executing the same SQL two times.
Is there a way I can access the context efficiently?
UPDATE:
After reading ListView method flowchart, I ended up doing this, but not sure if it's the best way. Feedback is appreciated.
object_list = []
context = None
def get_context_data(self, **kwargs):
return self.context
def get_queryset(self):
self.context = super(CourseListView, self).get_context_data()
profile = self.context['profile']
queryset = super(CourseListView, self).get_queryset()
queryset = queryset.filter(creator=profile)
self.context['object_list'] = queryset
return queryset
You can move getting profile out of get_context_data to upper function, like dispatch, or use cached_property decorator. This way your profile will be stored in _profile argument of view and you will not do second get to DB after calling self.profile second time.
from django.utils.functional import cached_property
class ProfileContextMixin(generic_base.ContextMixin, generic_view.View):
#cached_property
def profile(self):
return get_object_or_404(Profile, user__username=self.request.user)
def get_context_data(self, **kwargs):
context = super(ProfileContextMixin, self).get_context_data(**kwargs)
context['profile'] = self.profile
return context
class CourseListView(ProfileContextMixin, generic_view.ListView):
model = Course
template_name = 'course_list.html'
object_list = None
def get_queryset(self):
return super(CourseListView, self).get_queryset().filter(creator=self.profile)
I'm using the ModelFormSetView class in django-extra-views to create a formset view of all WorkerStatus entries connected to a Worker. I'd also like to use custom validation on the formset, so I've defined my own formset_class and form_class in the view. Here's the view definition:
class WorkerStatusUpdateView(ModelFormSetView):
model = WorkerStatusEntry
formset_class = WorkerStatusFormSet
form_class = WorkerStatusForm
template_name = 'staff/workers/worker_status_update.tmpl'
can_delete = True
can_order = False
fields = ['status', 'start_date']
def dispatch(self, request, *args, **kwargs):
self.worker = Worker.objects.get(pk=self.kwargs['worker_pk'])
return super(WorkerStatusUpdateView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, *args, **kwargs):
ctx = super(WorkerStatusUpdateView, self).get_context_data(*args, **kwargs)
ctx['worker'] = self.worker
return ctx
def get_queryset(self, *args, **kwargs):
return self.worker.statuses.all()
...and here are the definitions of the form and formset respectively:
class WorkerStatusForm(forms.ModelForm):
class Meta:
model = WorkerStatusEntry
fields = ['status', 'start_date']
class WorkerStatusFormSet(BaseModelFormSet):
class Meta:
model = WorkerStatusEntry
def __init__(self, queryset, *args, **kwargs):
super(WorkerStatusFormSet, self).__init__(*args, **kwargs)
def clean(self):
print "Cleaning"
This results in a page where EVERY WorkerStatusEntry in the database is shown in the formset, regardless of get_queryset(). One thing you'll notice is that WorkerStatusFormSet.__init__ takes a queryset argument: I put that there because there was a queryset argument passed to it from the ModelFormSetView, but I don't know what to do with it.
Another thing to note: if I take formset_class = WorkerStatusFormSet out of the view definition, the correct queryset shows up in the formset. However I need to use my own formset class to validate across the whole formset. Unless there's another way?
The problem is your WorkerStatusFormSet.__init__ method. Looking at the code for BaseModelFormSet, the __init__ method already takes a queryset parameter. Since you aren't doing anything in your __init__ method except calling super(), the easiest fix is to remove it.
It's not a good idea to change the signature of the __init__ method as you have done for two reasons
def __init__(self, queryset, *args, **kwargs):
super(WorkerStatusFormSet, self).__init__(*args, **kwargs)
You have changed the order of the arguments. If you look at the code for BaseModelFormset, the first argument is data. That means that data might be incorrectly assigned to queryset if somebody calls WorkerStatusFormSet(data, ...)
You do not do anything with queryset or pass it to super(), so it is lost.