Empty ModelFormset in Django's FormWizard - python

I'm using Django's FormWizard. It works fine but I'm having trouble getting any empty model formset to display correctly.
I have a model called Domain. I'm creating a ModelFormset like this:
DomainFormset = modelformset_factory(Domain)
I pass this to the FormWizard like this:
BuyNowWizardView.as_view([DomainFormset])
I don't get any errors but when the wizard renders the page, I get a list of all Domain objects. I'd like to get an empty form. How I can do this? I've read that I can give a queryset parameter to the ModelFormset like Domain.objects.none() but it doesn't seem to work as I get errors.
Any ideas on where I'm going wrong?
Thanks

The Django docs give two ways to change the queryset for a formset.
The first way is to pass the queryset as an argument when instantiating the formset. With the formwizard, you can do this by passing instance_dict
# set the queryset for step '0' of the formset
instance_dict = {'0': Domain.objects.none()}
# in your url patterns
url(r'^$', BuyNowWizardView.as_view([UserFormSet], instance_dict=instance_dict)),
The second approach is to subclass BaseModelFormSet and override the __init__ method to use the empty queryset.
from django.forms.models import BaseModelFormSet
class BaseDomainFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseDomainFormSet, self).__init__(*args, **kwargs)
self.queryset = Domain.objects.none()
DomainFormSet = modelformset_factory(Domain, formset=BaseDomainFormSet)
You then pass DomainFormSet to the form wizard as before.

Related

Django get custom queryset into ListView

I have a listview that I access in a pretty bog standard way to return all metaobjects.
#url
url(r'^metaobject/$', MetaObjectList.as_view(),name='metaobject_list'),
#ListView
class MetaObjectList(ListView):
model = MetaObject
I've recently added a search form that I want to scan my objects (I've got about 5 fields but I've simplified the example). What I'd like to do is re-use my MetaObjectList class view with my specific subset. I am guessing I need to override the get_queryset method but I'm not clear in how I get the queryset from my FormView into the listview. I mucked around a bit with calling the as_view() in the formveiw's form_valid function with additional parameters but couldn't get it to work and it seemed hacky anyway.
class SearchView(FormView):
template_name = 'heavy/search.html'
form_class = SearchForm
#success_url = '/thanks/'
def form_valid(self, form):
#build a queryset based on form
searchval=form.cleaned_data['search']
list = MetaObject.objects.filter(val=search)
#where to from here?
I also looked at trying to post the data from the form view over to the listview but that seemed like I'd need to re-write the form logic into the listview.
I'm on python 3.x and django 1.11.
I found what I feel is more elegant than the comment on the question:
My form valid now points to the list object's as_view method and passes the request and the queryset I want
def form_valid(self, form):
#build a queryset based on form
searchval=form.cleaned_data['search']
list = MetaObject.objects.filter(val=search)
return MetaObjectList.as_view()(self.request,list)
This hits the ListView as a post which I use to alter the queryset
class MetaObjectList(ListView):
model = MetaObject
queryset = MetaObject.objects.prefetch_related('object_type','domain')
def post(self, request, *args, **kwargs):
self.queryset = args[0]
return self.get(request, *args, **kwargs)
The only obvious change is using kwargs to make it a bit clearer. Otherwise this seems to work well.

Django FormPreview: Save form data to database

Probably a simple question but having trouble implementing a form preview page using django-formtools. I've configured everything per the docs. I'm stuck on what to add to the done() method to save the data to db.
forms.py
class JobForm(ModelForm):
class Meta:
model = Job
fields = ('title', 'category', 'company', 'website', 'description',)
class JobFormPreview(FormPreview):
def done(self, request, cleaned_data):
# add what here to save form data as object?
return HttpResponseRedirect('/success')
urls.py
...
url(r'^jobs/new/$',
JobFormPreview(JobForm),
name='job_form'),
...
Using the default templates. The form and preview both render fine, but obviously data doesn't save on submit. Tried self.form.save() per this answer but get an error save() missing 1 required positional argument: 'self'.
I appreciate any guidance.
Looking at the formtools code, it looks as if self.form is the form class, not the validated form instance. Therefore self.form.save() will not work. I've removed the suggestion to call self.form.save() from the linked answer.
I can't see a straight forward way to access the validated form in the done method. I suggest that you create the instance using the cleaned_data instead:
class JobFormPreview(FormPreview):
def done(self, request, cleaned_data):
job = Job.objects.create(**cleaned_data)
return HttpResponseRedirect('/success')

Django- rendering formset in template calls form _init_() 1 extra time

I have code snippets below, but the summary of my issue is this:
When displaying a formset with a known number of extra forms, each needing initialization with data from another object, the form's __init__() function is called for each form in the formset, and then one extra time. This causes an error, because the in the last call to __init__(), kwargs does not contain the expected item used for initialization.
My friends and I play a spreadsheet-based sports picking game which is very tedious to make changes to. I've wanted to learn Django for a while so I've been working on creating it as a webapp. Here's the relevant model for my issue:
class Pick(models.Model):
sheet = models.ForeignKey(Sheet)
game = models.ForeignKey(Game)
HOME = 'H'
AWAY = 'A'
PICK_TEAM_CHOICES = (
(HOME, 'Home'),
(AWAY, 'Away'),
)
pick_team = models.CharField(max_length=4,
choices=PICK_TEAM_CHOICES,
default=HOME)
... other stuff
And I've defined a form related to this model. The custom __init__() is so the form is initialized with information from a related Game object, passed with the 'initial' parameter in form creation:
class PickForm(ModelForm):
class Meta:
model = Pick
widgets = {'game': forms.HiddenInput()}
fields = ['sheet','game','amount','pick_type','pick_team']
def __init__(self, *args, **kwargs):
game = kwargs['initial']['game']
super(PickForm, self).__init__(*args, **kwargs)
self.fields['pick_team'].choices = ( ('H', game.home_team), ('A', game.away_team), )
I recently created the 'atomic' case where a user can pick a game via a PickForm in the related template, and that form is processed in the post() method of an adapted class based view. I'm trying to extend this case to handle multiple forms by creating a formset of PickForms:
class GameList(ListView):
template_name = 'app/games.html'
context_object_name = 'game_list'
def get_queryset(self):
games = get_list_or_404(Game, week = self.kwargs['week'])
return games
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#create a formset of PickForms
PickFormSet = formset_factory(PickForm, extra = len(context['game_list'])-1)
pick_list = []
sheet = Sheet.objects.get(user=self.request.user,league_week=self.kwargs['week'])
picks = Pick.objects.filter(sheet = sheet)
for index, game in enumerate(context['game_list'],start=0):
#logic to create a list of objects for initial data
#create the formset with the pick dictionary
context['pickforms'] = PickFormSet(initial=[{'game':pick.game,
'sheet':pick.sheet,
'amount':pick.amount,
'pick_type':pick.pick_type,
'pick_team':pick.pick_team,} for pick in pick_list])
return context
The get_context_data() in the view contructs the pick_list properly and initializes the PickFormSet- my issue occurs in the template. I'm letting Django handle the rendering so it's very simple right now:
<form action="{% url 'game_list' week %}" method="post">
{{ pickforms }}
<input type="submit" name="pickformset" value="Submit" />
</form>
It seems Django actually initializes the PickForms while rendering the template, because the problem occurs in the __init__() of my PickForm. When debugging, i can step through as it initializes a PickForm for each form in the formset- there are a total of 6 right now. So for 'form-0' (autogenerated prefix, I think) through 'form-5', the initialization works properly, because the kwargs dictionary contains an 'initial', as expected.
However, after initializing those 6 forms, it loops through the __init__() again, for a form with prefix 'form-6' (so a 7th form). This form has no initial data associated with it, and therefore errors out in the __init__() due to a KeyError.
Why is Django attempting to create another form? I have extra = 5 specified in the formset_factory call, so there should only be 6 forms total, each having a related initial data dictionary.
I thought it may be related to the included management_form of the formset, however explicitly rendering that, then using a for loop to iterate over the PickForms didn't work either- I ran into the same issue where the template engine is trying to initialize an extra form without any initial data.
Also: I tried using modelformset_factory and specifying PickForm, however in that case the PickForms seem to be initialized differently. There's no 'initial' data in kwargs, but an 'instance' instead, which behaves differently. I'm still new to Django so I'm confused as to why the two methods would pass different kwargs to the PickForm __init__()
Alright, after mulling this over all day, I decided to add a try-except block in my __init__() to just catch the KeyError thrown when kwargs doesn't have initial data.
def __init__(self, *args, **kwargs):
try:
game = kwargs['initial']['game']
super(PickForm, self).__init__(*args, **kwargs)
self.fields['pick_team'].choices = ( ('H', game.home_team),('A', game.away_team), )
except KeyError:
super(PickForm, self).__init__(*args, **kwargs)
Adding that enabled rendering of the formset, and I realized something that (to me) isn't obvious from the docs:
The number of extra forms specified in the formset are created in addition to any forms defined by initial data. My interpretation of the docs was that I'd need enough extra forms to cover however many forms I wanted to initialize with desired data. In hindsight, that sort of bookeeping could be annoying- it's nice that your initial list of dictionaries can vary in length without having to worry about specifying the correct extra.
So, the formset initializes a form for every dictionary in initial list of dictionaries, then creates extra number of blank forms.
I feel rather dumb, but at the same time I don't think this specific case is all that clear in the documentation.
EDIT: On closer reading, there is some text that clearly says extra forms are created in addition to the number of forms generated by initial data. Conclusion: I need to learn to read

Django Class Based View UpdateView Restricted User

I am trying to use a Django UpdateView to display an update form for the user. https://docs.djangoproject.com/en/1.8/ref/class-based-views/generic-editing/
I only want the user to be able to edit their own form.
How can I filter or restrict the the objects in the model to only show objects belonging to the authenticated user?
When the user only has one object I can use this:
def get_object(self, queryset=None):
return self.request.user.profile.researcher
However, I now need the user to be able to edit multiple objects.
UPDATE:
class ExperimentList(ListView):
model = Experiment
template_name = 'part_finder/experiment_list.html'
def get_queryset(self):
self.researcher = get_object_or_404(Researcher, id=self.args[0])
return Experiment.objects.filter(researcher=self.researcher)
class ExperimentUpdate(UpdateView):
model = Experiment
template_name = 'part_finder/experiment_update.html'
success_url='/part_finder/'
fields = ['name','short_description','long_description','duration', 'city','address', 'url']
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
return qs.filter(researcher=self.request.user.profile.researcher)
URL:
url(r'^experiment/update/(?P<pk>[\w\-]+)/$', login_required(ExperimentUpdate.as_view()), name='update_experiment'),
UpdateView is only for one object; you'd need to implement a ListView that is filtered for objects belonging to that user, and then provide edit links appropriately.
To prevent someone from simply putting the URL for an edit view explicitly, you can override get_object (as you are doing in your question) and return an appropriate response.
I have successfully been able to generate the list view and can get
the update view to work by passing a PK. However, when trying to
override the UpdateView get_object, I'm still running into problems.
Simply override the get_queryset method:
def get_queryset(self):
qs = super(ExperimentUpdate, self).get_queryset()
# replace this with whatever makes sense for your application
return qs.filter(user=self.request.user)
If you do the above, then you don't need to override get_object.
The other (more complicated) option is to use custom form classes in your UpdateView; one for each of the objects - or simply use a normal method-based-view with multiple objects.
As the previous answer has indicated, act on the list to show only the elements belonging to the user.
Then in the update view you can limit the queryset which is used to pick the object by overriding
def get_queryset(self):
qs = super(YourUpdateView, self).get_queryset()
return qs.filter(user=self.request.user)

Django multi model best practice

Basically what I'm trying to achieve is a multi-model django app where different models take advantage of the same views. For example I've got the models 'Car' 'Make' 'Model' etc and I want to build a single view to perform the same task for each, such as add, delete and edit, so I don't have to create a seperate view for add car, ass make etc. I've built a ModelForm and Model object for each and would want to create a blank object when adding and a pre-populated form object when editing (through the form instance arg), with objects being determined via url parameters.
Where I'm stuck is that I'm not sure what the best way to so this is. At the moment I'm using a load of if statements to return the desired object or form based on parameters I'm giving it, which get's a bit tricky when different forms need specifying and whether they need an instance or not. Although this seems to be far from the most efficient way of achieving this.
Django seems to have functions to cover most repetitive tasks, is there some magic I'm missing here?
edit - Here's an example of what I'm doing with the arguments I'm passing into the url:
def edit_object(request, object, id):
if(object==car):
form = carForm(instance = Car.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
What about using Class Based Views? Using CBVs is the best way in Django to make reusable code. For this example maybe it can be a little longer than function based views, but when the project grows up it makes the difference. Also remember "Explicit is better than implicit".
urls.py
# Edit
url(r'^car/edit/(?P<pk>\d+)/$', EditCar.as_view(), name='edit-car'),
url(r'^make/edit/(?P<pk>\d+)/$', EditMake.as_view(), name='edit-make'),
# Delete
url(r'^car/delete/(?P<pk>\d+)/$', DeleteCar.as_view(), name='delete-car'),
url(r'^make/delete/(?P<pk>\d+)/$', DeleteMake.as_view(), name='delete-make'),
views.py
class EditSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-edit.html'
class EditCar(EditSomethingMixin, UpdateView):
model = Car
form_class = CarForm
class EditMake(EditSomethingMixin, UpdateView):
model = Make
form_class = MakeForm
class DeleteSomethingMixin(object):
"""Use Mixins to reuse common behavior"""
template_name = 'template-delete.html'
class DeleteCar(DeleteSomethingMixin, DeleteView):
model = Car
class DeleteMake(DeleteSomethingMixin, DeleteView):
model = Make
Just pass your class and form as args to the method then call them in the code.
def edit_object(request, model_cls, model_form, id):
form = model_form(instance = model_cls.objects.get(pk=id)
return render(request, 'template.html', {'form':form})
then just pass in the correct classes and forms in your view methods
def edit_car(request,id):
return edit_object(request, Car, CarForm, id)
each method knows what classes to pass, so you eliminate the if statements.
urls.py
url(r'^car/delete/(?<pk>\d+)/', edit, {'model': Car})
url(r'^make/delete/(?<pk>\d+)/', edit, {'model': Make})
views.py
def edit(request, id, model):
model.objects.get(id=id).delete()

Categories

Resources