Can I use standard generic CreateView for model formset? - django

I am again stuck with model formset. I have following form:
ArticleFormSet = modelformset_factory(Article, fields=('title', 'pub_date'), extra=3)
and View for this model formset:
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleFormSet
template_name = 'article_form_view.html'
success_url = "/"
Reading documentation here: https://docs.djangoproject.com/en/4.0/topics/forms/modelforms/#model-formsets
I understand that, I can use ArticleFormSet instead of standard form for form_class attribute.
So basically, what I am expecting is, this should display 3 forms for the same Article model and I should be able to create three articles at the same time.
But I am getting "TypeError: init() got an unexpected keyword argument 'instance'
".
So the question is, is this how it supposed to work? Or what would be the correct way of creating few articles at the same time?

I do not think that the generic CreateView is by default capable of handling formsets; however, it should be easier to customize the view so it works as you intend.
From what I see the exception is raised when the view tries to create the form to render. So overriding the get_form method is required:
class ArticleCreateView(CreateView):
...
def get_form(self):
if self.request.method == 'POST':
return self.form_class(self.request.POST)
return self.form_class()
The method either returns the modelformset as created by the factory (in case of calling the formset) or fills it with your posted data.
One more issue seems to be the success_url. You will get an expception when you use a formset. So you also need to override that method, e.g.
...
def get_success_url(self):
return self.success_url
Now it should work.
However: If I understand you correctly, you want the view to be used for creating new data. The `` modelformset_factory``` fetches all your model instance and appends 3 empty forms. This might not be what you want. In order to get an empty formset look at this SO post:
Empty ModelFormset in Django's FormWizard

Related

How to add context data to Django CreateView when form is valid

Using Django, I have a page with a form that is rendered using CreateView. The success_url is the same url as the original form. When the form is submitted and is valid, I want to insert some data into the context.
I'd like to know the most flexible and clear way to add this context data, because the example below (which works) feels sub-optimal for such a (presumably) common task. I'm looking for a more general solution that using SuccessMessageMixin.
class DemoView(CreateView):
template_name = "request-demo.html"
form_class = DemoForm
success_url = reverse_lazy("base:demo")
def form_valid(self, form):
create_and_send_confirmation_email(form)
return self.render_to_response(self.get_context_data(success_message=True))
Most generically you will define the succcess_url to be the DetailView of the object you have just created. In fact, if you do not define a success_url at all, the default get_success_url will return
url = self.object.get_absolute_url()
which is commonly the same thing.
If you want to do something special for a newly created object you might
Redirect to a different URL, such as a subclass of your object DetailView with a custom get_context_data method and/or template name. Or possibly, somewhere completely differerent.
Interrogate the newly created object (in the template and/or get_context_data method) to establish in some application-specific way that it is indeed newly created.

Django: how to re-create my function-based view as a (Generic Editing) Class Based View

I have a function-based view that is currently working successfully. However, I want to learn how to create the equivalent Class Based View version of this function, using the generic UpdateView class -- though I imagine the solution, whatever it is, will be the exact same for CreateView, as well.
I know how to create and use Class Based Views generally, but there is one line of my function-based view that I have not been able to work into the corresponding UpdateView -- as usual with the Generic Editing Class Based Views, it's not immediately clear which method I need to override to insert the desired functionality.
The specific task that I can't port-over to the CBV, so to speak, is a line that overrides the queryset that will be used for the display of a specific field, one that is defined as ForeignKey to another model in my database.
First, the working function-based view, with highlight at the specific bit of code I can't get working in the CVB version:
#login_required
def update_details(request, pk):
"""update details of an existing record"""
umd_object = UserMovieDetail.objects.select_related('movie').get(pk=pk)
movie = umd_object.movie
if umd_object.user != request.user:
raise Http404
if request.method != 'POST':
form = UserMovieDetailForm(instance=umd_object)
# this is the single line of code I can't get working in UpdateView version:
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
else:
form = UserMovieDetailForm(instance=umd_object, data=request.POST)
if form.is_valid():
form.save()
return redirect(movie)
context = {'form': form, 'object': umd_object }
return render(request, 'movies/update_details.html', context)
I can recreate every part of this function-based view in UpdateView successfully except for this line (copied from above for clarity):
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
What this line does: the default Form-field for a ForeignKey is ModelChoiceField, and it by default displays all objects of the related Model. My code above overrides that behavior, and says: I only want the form to display this filtered set of objects. It works fine, as is, so long as I'm using this function-based view.
Side-Note: I am aware that this result can be achieved by modifying the ModelForm itself in my forms.py file. The purpose of this question is to better understand how to work with the built-in Generic Class Based Views, enabling them to recreate the functionality I can already achieve with function-based views. So please, refrain from answering my question with "why don't you just do this in the form itself instead" -- I am already aware of this option, and it's not what I'm attempting to solve, specifically.
Now for the UpdateView (and again, I think it would be the same for CreateView). To start off, it would look essentially like this:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
model = UserMovieDetail
template_name = 'movies/update_details.html'
form_class = UserMovieDetailForm
login_url = 'login' # used by LoginRequiredMixin
# what method do I override here, to include that specific line of code, which needs
# to occur in the GET portion of the view?
def get_success_url(self):
return reverse('movies:movie', kwargs={'pk': self.object.movie.pk, 'slug': self.object.movie.slug })
The above is a working re-creation of my function-based view, replicating all the behavior except that one important line that filters the results of a specific field's ModelChoiceField display in the Form.
How do I get that line of code to function inside this UpdateView? I've reviewed the methods built-in to UpdateView on the classy class-based views website, and then attempted (by pure guess-work) to over-ride the get_form_class method, but I it didn't work, and I was basically shooting in the dark to begin with.
Note that since the functionality I want to re-create is about the display of items in ModelChoiceField of the form, the desired behavior applies to the GET portion of the view, rather than the POST. So I need to be able to override the form fields before the form is rendered for the first time, just like I did in my function based view. Where and how can I do this in UpdateView?
First, a note not related to form - from raise Http404 in functional view I understand that you want to allow user to access only his own movies. For that in class based view you can override get_queryset method:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
def get_queryset(self):
return UserMovieDetail.objects \
.filter(user=request.user) \
.select_related('movie')
Now let's move to customizing form.
Option 1 - .get_form()
You can override get_form method of the UpdateView:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form(self, form_class=None):
form = super().get_form(form_class)
# add your customizations here
round = self.object.movie.game_round
form.fields['user_guess'].queryset = \
User.objects.filter(related_game_rounds=round)
return form
Option 2 - moving customizations to form class and .get_form_kwargs()
You might prefer to move customization logic from view to form. For that you can override form's __init__ method. If customization logic requires extra information (for example, queryset depends on current user), then you can also override get_form_kwargs method to pass extra parameters to the form:
# views.py
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'current_user': self.request.user})
return kwargs
# forms.py
class UserMovieDetailForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop('current_user')
super().__init__(*args, **kwargs)
# add your customizations here
self.fields['user_guess'].queryset = ...
P.S. In general, great resource for understanding django class based views is https://ccbv.co.uk/

Custom UpdateView in Python

I am trying to get a custom UpdateView to work in Python/Django. I believe that the code that I've writtten is mostly correct, as it seems to be returning the proper Primary Key ID in the URL when I click on the associated dropdown. The problem is that I am not seeing any of the data associated with this record on the screen in update mode. The screen appears in edit mode, but there is no data. I suspect the problem is perhaps the django template in the html form? However, I have played with the form and used {{ form }} and it too returns a blank form. I've played with this all afternoon and I'm out of guesses. Here is my view:
def updating_document(request, pk):
doc = get_object_or_404(Doc, pk=pk)
form = Update_Doc_Form(request.user, request.POST)
if request.method == 'GET':
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('App:main_procedure_menu'))
else:
print("Form is invalid!")
return render(request,'Doc/update_doc.html',{'form':form })
I also have an associated form...
Form.py
class Update_Doc_Form(forms.ModelForm):
class Meta:
model = Doc
exclude = ['user']
doc_name = forms.CharField(widget=forms.TextInput)
description = forms.CharField(required=True,widget=forms.Textarea)
team = forms.CharField(widget=forms.Select)
document = forms.CharField(required=True,widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super(Update_Doc_Form, self).__init__(*args, **kwargs)
self.fields['doc_name'].widget.attrs['class'] = 'name'
self.fields['description'].widget.attrs['class'] = 'description'
self.fields['team'].widget.attrs['class'] = 'choices'
self.fields['team'].empty_label = ''
I'm a newbie, but I do want to use a custom UpdateView so that I can alter some of the fields and pass user information. I feel like the code is close, just need to figure out why it's not actually populating the form with data. Thanks in advance for your help!
What a difference a day makes. Found an answer on SO this morning. Not sure how to credit the person or issue number....
The answer was to add the following line of code to my form:
user = kwargs.pop('object_user')
I also needed to add the following function to my View:
def get_form_kwargs(self):
kwargs = super(ViewName,self).get_form_kwargs()
kwargs.update({'object_user':self.request.user})
return kwargs
This question was answered originally in 2013 by Ivan ViraByan. Thanks Ivan!
I ultimately went with a standard class based UpdateView and scrapped my plans for the custom UpdateView once I was able to figure out how to use the Class Based View(UpdateView) and "pop" off the user information when passing it to the form based on Ivan ViraByan's answer in 2013.
The code above allows you to get the user but not pass it to the ModelForm so that you don't get the unexpected user error.

Handling POST request when using ModelChoiceView

My simple form looks like this:
class PropertyFilterForm(forms.Form):
property_type = forms.ModelChoiceField(queryset=Property.objects.values_list('type', flat=True).order_by().distinct())
property_type returns a flat list of String values to the drop-down list in my template.
Now, when i choose one of the values and hit "Submit" - i get the following error:
Select a valid choice. That choice is not one of the available
choices.
My view at the moment looks like this:
def index(request):
if request.method == 'POST':
form = PropertyFilterForm(request.POST)
if form.is_valid():
selected_type = form.cleaned_data['property_type']
properties = Property.objects.filter(type=selected_type)
else:
form = PropertyFilterForm()
properties = Property.objects.all()
return render(request, 'index.html', context=locals())
I read and re-read this question many times. It seems to be the same thing, but still I wasn't able to figure out the exact solution.
What I understood so far, is that I need to either explicitly specify a queryset for my form each time I call it in the view, or (better) specify the queryset in the init method of my form.
Could please someone elaborate on whether we need to specify a queryset the way i described above?
If yes, why? Haven't we already specified it in the form definition?
I would be really grateful for any code snippets
You want the user to select a string, not a property instance, so I think it would be a better fit to use a ChoiceField instead of a ModelChoiceField.
class PropertyFilterForm(forms.Form):
property_type = forms.ChoiceField(choices=[])
def __init__(self, *args, **kwargs):
super(PropertyFilterForm, self).__init__(*args, **kwargs)
self.fields['property_type'].choices = Property.objects.values_list('type', 'type').order_by('type').distinct()
The disadvantage of using a ChoiceField is we need to generate the choices in the __init__ method of the form. We have lost the nice functionality of the ModelChoiceField, where the queryset is evaluated every time the form is created.
It's not clear to me why Daniel recommended sticking with ModelChoiceField rather than ChoiceField. If you were to use ModelChoiceField, I think you'd have to subclass it and override label_from_instance. As far as I know, using values() is not going to work.
To specify the initial value, you can either hardcode it in the form definition,
class PropertyFilterForm(forms.Form):
property_type = forms.ChoiceField(choices=[], initial='initial_type')
or set it in the __init__ method,
self.fields['property_type'].initial = 'initial_type'
or provide it when instantiating the form:
form = PropertyFilterForm(request.POST, initial={'property_type': 'initial_type'})

Using class-based UpdateView on a m-t-m with an intermediary model

How can I convince a Django 1.3 class based generic view:
UpdateView.as_view(model=Category,
template_name='generic_form.html',
success_url='/category/')
To not give up so easy with error:
"Cannot set values on a ManyToManyField which specifies an intermediary model."
Even if all fields in the intermediary model have defaults, I can't get the class based generic view to save. The functional based version looks messy also. Django 1.3.
As Berislav Lopac says:
class CategoryView(UpdateView):
model=Category
def form_valid(self, form):
self.object = form.save(commit=False)
IntermediateModel.objects.filter(category = self.object).delete()
for other_side_model_object in form.cleaned_data['other_side_model_field']:
intermediate_model = IntermediateModel()
intermediate_model.category = self.object
intermediate_model.other_side_model_related_field= other_side_model_object
intermediate_model.save()
return super(ModelFormMixin, self).form_valid(form)
I answer some similar here.
You should extend UpdateView and override the form_valid() method to manually save the intermediary model.
Personally, I never use generic views directly from the URL pattern, I always extend them verbatim in views.py.