Combining two separate forms in one Django view? - python

This question have been answered before, e.g here: Proper way to handle multiple forms on one page in Django
So before it gets marked as a duplicate. I'll try to explain why its different.
I've got three tables, Project, ProjectUser and User. ProjectUser is a join table to indicate what users belongs to what project.
I'm trying to create a view that lets users update project details (e.g. name of project), and also add users to the project (which is indicated by a dropdown that shows all available users like the standard one for models with foreign keys in the django admin panel). All works fine until I'm trying to pass an id from the views to the formclass and submit.
views.py
class ProjectUpdateView(UpdateView):
form_class = ProjectUpdateForm
second_form_class = ProjectUserAddForm
template_name = 'projects/project_edit.html'
success_url = reverse_lazy('projects:list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
id_ = self.kwargs.get("id")
project = Project.objects.get(id=id_)
if 'form' not in context:
context['form'] = self.form_class()
if 'form2' not in context:
team = Organization.objects.get(id=project.organization_id)
context['form2'] = self.second_form_class(queryset=team) # <-- here is where I wish to pass a queryset, which fails when trying to submit form2.
context['project_users'] = ProjectUser.objects.filter(project__id=project.id).select_related("project")
context['team'] = Organization.objects.get(id=project.organization_id)
return context
def get_object(self):
id_ = self.kwargs.get("id")
return get_object_or_404(Project, id=id_)
def form_invalid(self, **kwargs):
return self.render_to_response(self.get_context_data(**kwargs))
def form_valid(self, form):
project_id = self.kwargs.get("id")
if self.request.POST.get("form2") == 'Add':
ProjectUser.objects.create(user_id=self.request.POST.get("user"), project_id=project_id)
form.save()
success_url = reverse("projects:edit", args=(project_id,))
return HttpResponseRedirect(success_url)
def post(self, request, *args, **kwargs):
# get the user instance
self.object = self.get_object()
# determine which form is being submitted
# uses the name of the form's submit button
if 'form' in request.POST:
# get the primary form
form_class = self.get_form_class()
form_name = 'form'
else:
# get the secondary form
form_class = self.second_form_class
form_name = 'form2'
# get the form
form = self.get_form(form_class)
# validate
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(**{form_name: form})
projects_edit.html
<form action="{% url 'projects:edit' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form.name|as_crispy_field}}
<input name="form" value="Update" type="submit"></input>
</form>
<form action="{% url 'projects:edit' project.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form2.user}}
<input name="form2" value="Add" type="submit"></input>
</form>
forms.py
class ProjectUpdateForm(ModelForm):
class Meta:
model = Project
fields = ["name"]
class ProjectUserAddForm(ModelForm):
def __init__(self, queryset, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['user'].queryset = User.objects.filter(organizations_organizationuser__organization__id=queryset.id) # here is where I wish to pass the id of the queryset from the form class
class Meta:
model = ProjectUser
fields = ["user"]
Rendering the forms works just fine with the desired queryset, but when I try to submit the second form (adding a user to the ProjectUserForm, I just get a
__init__() missing 1 required positional argument: 'queryset' error.
Any ideas on how to solve this? Perhaps I'm making it way more complicated than it should
I have also added a screenshot if it helps: https://imgur.com/a/uqu0UeB

Related

How to access a foreign key related field in a template when using Django model form

My Objective
Access the field name in the Parent Model ParentModel and display its content in a form instance in the template. For example, let the field parent be a foreign key in the ChildModel as described below.
What I have tried
Access the parent field in the form as {{ form.parent.name }} in the template
Errors received
Tried looking up form.parent.name in context
models.py
class ParentModel(models.Model):
name = models.CharField()
def __str__(self):
return self.name
class ChildModel(models.Model):
parent = models.ForeignKey(ParentModel)
def __str__(self):
return self.parent.name
forms.py
class ChildModelForm(ModelForm):
class Meta:
model = ChildModel
fields = '__all__'
widgets = {'parent': forms.Select(),}
views.py
def childView(request, pk):
template = 'template.html'
child = ChildModel.objects.get(parent=pk)
form = ChildModelForm(instance=child)
if request.method == 'POST':
form = ChildModelForm(request.POST, instance=child)
if form.is_valid():
form.save()
else:
form = ChildModelForm(instance=child)
context = {'form': form, }
return render(request, template, context)
template.html
<form method="POST" action="">
{% csrf_token %}
{{form.parent.name}}
<button type="submit">Save</button>
</form>
Now the child model form displays pk I want to display the name of the parent field
I have also tried using this Django access foreignkey fields in a form but it did not work for me.
From my understanding, you want to display the form instance's values. You can do:
form.instance.parent.name

django: unable to save new user

I'm trying to build a view that shows a form for creating a new user.
Next are the template I'm using, urls.py and the code attempts at views.py and forms.py.
user_form.html
<form action="{% url 'app:register' %}" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Register">
</form>
urls.py
urlpatterns = [
...
path('newuser/',views.NewUser.as_view(), name='newuser'),
...
]
First attempt:
views.py
class NewUser(generic.CreateView):
model = User
fields = ['username','email','password']
template_name = 'app/user_form.html'
success_url = reverse_lazy('register')
This one didn't return errors, but was unable to save the new user data.
Second try involved creating a form for the occasion:
forms.py
class NewUserForm(forms.Form):
username = forms.CharField(max_length=100)
email = forms.EmailField()
password = forms.PasswordInput()
views.py
class NewUser(generic.CreateView):
model = User
form_class = NewUserForm
template_name = 'app/user_form.html'
success_url = reverse_lazy('register')
This one returned an error: TypeError at /newuser/ BaseForm.__init__() got an unexpected keyword argument 'instance'
Third:
forms.py
class NewUserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username','email','password']
views.py
class NewUser(generic.CreateView):
model = User
form_class = NewUserForm
template_name = 'app/user_form.html'
success_url = reverse_lazy('cadastro')
Can't save a new User instance either.
I've also noticed that the model = User line in this last try can be removed.
I'm using the User.objects.all() query in the terminal and the admin page to check for new users.
What am I not doing?
You should make the POST request to the newuser view, so:
<form action="{% url 'app:newuser' %}" method="post">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Register">
</form>
You can however not use a simple ModelForm: passwords in Django are hashed and the ModelForm will not do that, or at least not automatically.
You can make use of the UserCreationForm [Django-doc] to do the hashing properly, this field also uses two password fields which will be validated.
If you want to implement a custom ModelForm, then you will need to implement the password hashing functionality in the model form:
class NewUserForm(forms.ModelForm):
class Meta:
model = User
fields = ['username','email','password']
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data['password'])
if commit:
user.save()
return user
and then plug this into the CreateView with:
class NewUserView(generic.CreateView):
model = User
form_class = NewUserForm
template_name = 'app/user_form.html'
success_url = reverse_lazy('cadastro')
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to NewUserView, instead of NewUser.
when you use CreateView on a model, it calls create method.
but User model is different. User model doesn't have create, instead have create_user. and CreateView doesn't know about it.
i suggest that you use View class like this:
class UserRegisterView(View):
form_class = UserRegistrationForm
template_name = 'account/register.html'
def dispatch(self, request, *args, **kwargs):
if request.user.is_authenticated:
return redirect('home:home')
return super().dispatch(request, *args, **kwargs)
def get(self, request):
form = self.form_class()
return render(request, self.template_name, {'form':form})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
cd = form.cleaned_data
User.objects.create_user(cd['username'], cd['email'], cd['password1'])
messages.success(request, 'you registered successfully', 'success')
return redirect('home:home')
return render(request, self.template_name, {'form':form})
this snippet totally works.

How to filter a model through multiple forms?

I have a single Car model which I'd like to filter through interdependent ModelChoiceField's:
class Car(models.Model):
make = models.CharField(max_length=50)
model = models.CharField(max_length=50)
platform = models.CharField(max_length=50)
Forms.py:
class MakeSelectForm(forms.ModelForm):
make = forms.ModelChoiceField(queryset=Car.objects.values_list('make',flat=True).distinct())
class Meta:
model = Car
fields = ["make"]
class ModelSelectForm(forms.ModelForm):
model = forms.ModelChoiceField(queryset=Car.objects.values_list('model',flat=True).distinct())
class Meta:
model = Car
fields = ["make", "model"]
Views.py:
def make_select_view(request):
form = MakeSelectForm()
make = None
if request.method == "POST":
form = MakeSelectForm(request.POST)
if form.is_valid():
make = form.cleaned_data['make']
return render(request, "reviews/makeselect.html", {"form": form, "make": make})
def model_select_view(request, make):
form = ModelSelectForm()
model = None
if request.method == "POST":
form = MakeSelectForm(request.POST)
if form.is_valid():
model = form.cleaned_data['model']
return render(request, "reviews/modelselect.html", {"form": form, "model": model})
URL's:
urlpatterns = [
url(r'^$', views.make_select_view, name="make-select"),
url(r'^(?P<make>\w+)/$', views.model_select_view, name="model-select"),
]
Makeselect.html:
<form action="{% url 'reviews:model-select' make %}" method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Select" />
</form>
Now, I have to pass "make" argument of the first form when posted, to the second view, and then use it to filter through Car instances with that make. But here all I pass is "None", and get Select a valid choice. That choice is not one of the available choices. error in the second form.
Any suggestion or feedback will be welcomed and greatly appreciated.
Thank you.
First point: model forms are for creating / editing models, so you should use plain forms here. Your error comes from having the make field in your ModelSelectForm but not setting its value anywhere. Also, ModelChoiceField is meant to retrieve a model instance, not field's value, so you really want a ChoiceField here.
Second point, since your goal is to display filtered informations - not to create or edit anything -, you should use GET queries (like for any "search" feature actually).
For your second form to work as expected (once ported to a plain Form with a single model field), you'll need to pass the make value to the form and, in the form's __init__(), update the model fields choices to the filtered queryset.
Also since you'll be using GET as form's method, you'll have to check wether the form has been submitted at all before deciding to instanciate it with or without the request.GET data, else the users would get error messages on first display before they even had a chance to submit anything. This is usually solved using either a name and value for the form's submit button or a hidden field in the form itself:
Forms:
class ModelSelectForm(forms.Form):
model = forms.ChoiceField()
def __init__(self, *args, **kwargs):
make = kwargs.pop("make", None)
if not make:
raise ValueError("expected a 'make' keyword arg")
super(ModelSelectForm, self).__init__(*args, **kwargs)
qs = Car.objects.filter(make=make).values_list('model',flat=True).distinct()
choices = [(value, value) for value in qs]
self.fields["model"].choices = choices
Views:
def model_select_view(request, make):
model = None
if request.GET.get("submitted", None):
form = ModelSelectForm(request.GET, make=make)
if form.is_valid():
model = form.cleaned_data['model']
else:
form = ModelSelectForm(make=make)
context = {"form": form, "model": model, "make: make}
return render(request, "reviews/modelselect.html", context)
Templates:
<form action="{% url 'reviews:model-select' make %}" method="GET">
{% csrf_token %}
<input type="hidden" name="submitted" value="1" />
{{ form.as_p }}
<input type="submit" value="Select" />
</form>
wrt/ your question about "passing 'make' to the second view": there's nowhere in your code snippet where you direct the user to the model-select view, but I assume that what you want is the user being redirected to it once he successfully selected the "make" in the first view. If yes, your first view's code should handle the case on successful form submission, ie:
def make_select_view(request):
if request.GET.get("submitted", None):
form = MakeSelectForm(request.GET)
if form.is_valid():
make = form.cleaned_data['make']
# send the user to the model selection view
return redirect("reviews:model-select", make=make)
else:
form = MakeSelectForm()
context = {"form": form}
return render(request, "reviews/makeselect.html", context)
Since the formatting of the snippet that I posted in comment got messed up so, I am writing that as an answer here.
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(GameForm, self).__init__(*args, **kwargs)
if not self.request.user.is_staff:
self.fields['publisher'].queryset = Publisher.objects.filter(id=self.request.user.id)

take select request from django from

I have made a select django form with list of images for each user.
How do I take that select request in my views.py ?
I have only managed to create correct that select list but I need to take that select request. But I don't know how.
models.py
class MyModel(models.Model):
user = models.ForeignKey(User, unique=True)
upload = models.ImageField(upload_to='upload')
views.py
#login_required(login_url="login/")
def carlist(request):
Myform = MyModelForm(user=request.user)
return render(request,'about.html',{'Myform':Myform})
select django form :
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
# extract "user" from kwrags (passed upon form init)
if 'user' in kwargs:
self.user = kwargs.pop('user')
super(MyModelForm, self).__init__(*args, **kwargs)
# generate the choices as (display, value).
# Display is the one that'll be shown to user, value is
# the one that'll be sent upon submitting
# (the "value" attribute of <option>)
choices = MyModel.objects.filter(user=self.user).values_list('upload', 'id')
self.fields['upload'].widget = Select(choices=choices)
class Meta:
model = MyModel
fields = ('upload',)
html :
<form class="" action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ Myform}}
<input type="submit" name="" value="Submit">
for example Myform now has a list of user images, which is correct but after that i need the selected images from the form.
Can do it that with my code or not?
Yes you can do it with your code. In your view, you can get it as:
class MyView(View):
def post(self, request):
form = MyModelForm(request.POST, request.FILES)
if form.is_valid():
upload_inst = form.cleaned_data['upload']
...
This will give you the selected image from the form.
If you want to have multiple images being selected, you could try the following:
https://pypi.python.org/pypi/django-multiselectfield/
https://pypi.python.org/pypi/django-select-multiple-field/

Django: copy a model object using views and forms

I have a Django (1.8) model that has some class based generic views: list, update, delete, detail, create.
https://docs.djangoproject.com/en/1.8/ref/class-based-views/
On the detail or list view, I have a button that I want to do this:
Create a copy of the object
Load the data into a form and display for the user to edit/save the new object (could use the existing update or create views, or a new one?)
I can clone the model with this info:
How do I clone a Django model instance object and save it to the database?
But I can't make the leap to get this done by starting at a view and ending at a form with the copied object data.
Thanks!
partial views.py
class List(ListView):
model = Announcement
template_name = 'announcements/list.html'
class Create(CreateView):
model = Announcement
form_class = AnnouncementForm
template_name = 'announcements/form.html'
def form_valid(self, form):
data = form.save(commit=False)
data.author = self.request.user
data.save()
return super(Create, self).form_valid(form)
class Update(UpdateView):
model = Announcement
form_class = AnnouncementForm
template_name = 'announcements/form_update.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(Update, self).dispatch(*args, **kwargs)
partial forms.py
class AnnouncementForm(forms.ModelForm):
class Meta:
model = Announcement
exclude = ['author']
partial list.html
{% for object in object_list %}
<p>object.title</p>
<a class="btn btn-danger" href="{% url 'announcements:delete' object.id %}" role="button">Delete</a>
<a class="btn btn-info" href="{% url 'announcements:update' object.id %}" role="button">Edit</a>
<a class="btn btn-primary" href="" role="button">Copy</a>
{% endfor %}
What I hit the "Copy" button in list.html, I want to duplicate the object and open the new duplicate in a form for editing.
It think I figured it out!
urls.py
#eg: myapp/5/copy/
#where 5 is the item I want to copy
url(r'^(?P<id>[0-9]+)/copy/$', views.item_copy, name='item_copy'),
views.py:
def item_copy(request, id):
new_item = get_object_or_404(MyModel, pk = id)
new_item.pk = None #autogen a new pk (item_id)
new_item.name = "Copy of " + new_item.name #need to change uniques
form = MyForm(request.POST or None, instance = new_item)
if form.is_valid():
form.save()
return redirect('my_view')
context = {
"form": form,
#other context
}
return render(request, "form.html", context)
class CopyView(ManageAnnouncement, DeleteView):
def dispatch(self, *args, **kwargs):
obj = self.get_object()
obj.pk = None
copy = obj.save()
return HttpResponseRedirect('/announcement/edit/%s' %(copy.id))
# Change the redirect page to the one you need.
I have inherited a base class called ManageAnnouncement. You can put methods or variables common to multiple classes in an abstract base class and inherit it in adding, editing deleting, copying etc, so that code gets 'dry'.

Categories