I found multiple answers to this same questions but unfortunately, I can't seem to figure it out :(
The form has a drop-down list for the 'subcategory' field in my model 'PhysicalPart', the values of the 'subcategory' field are updated dynamically upon the form creation (using a 'category' parameter).
Unfortunately, I can't get the drop-down to show all subcategories AND have the one from the database selected at the same time. I can't seem to retrieve the 'short_description' value either from the database.
It used to work before I learned about UpdateView class and decided to use it instead...
Any insight on how-to workaround my problem would be appreciated!
forms.py
class PartForm(forms.ModelForm):
subcategory = forms.ChoiceField(choices=[])
class Meta:
model = PhysicalPart
fields = ['subcategory', 'short_description']
views.py
class PartUpdate(UpdateView):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
def post(self, request, *args, **kwargs):
# Load model instance
self.object = self.get_object()
# Load form
form = super(PartUpdate, self).get_form(self.form_class)
# Populating subcategory choices
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form valid and save data
if form.is_valid():
form.save()
return redirect('part-list')
# Update context before rendering
context = self.get_context_data(**kwargs)
context['part_id'] = self.object.pk
context['part_category'] = self.object.category
context['manufacturing_list'] = self.object.manufacturing.all()
return render(request, self.template_name, context)
html
<form action="{% url 'part-update' pk=part_id category=part_category %}" method="post" style="display: inline">
{% csrf_token %}
<div class="form">
<p class="font-weight-bold">Type</br>
{{ form.subcategory }}
</p>
</div>
<div class="form">
<p class="font-weight-bold">Short Description</br>
{{ form.short_description }}
</p>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
<form action="{% url 'part-list' %}" style="display: inline">
<button type="submit" class="btn btn-danger">Cancel</button>
</form>
My problem was that I did not differentiate the "GET" versus the "POST" calls in the UpdateView class, I was trying to do everything in the post() method. It took me a while to figure it out but now I think it's clear.
I originally used the get() method but I realize that get_context_data() was better suited as it automatically loads most of the context (eg. the instance and the form), instead of having to do everything from scratch in the get() method.
Scrubbing through the code of the UpdateView class here, it also seemed necessary to add ModelFormMixin into the declaration of the PartUpdate class so that the get_context_data() method automatically loads the form associated to the target model/instance (else it looks like it won't do it).
Here is my updated views.py code:
class PartUpdate(UpdateView, ModelFormMixin):
model = PhysicalPart
template_name = 'part_update.html'
form_class = PartForm
success_url = reverse_lazy('part-list')
def get_context_data(self, **kwargs):
# Load context from GET request
context = super(PartUpdate, self).get_context_data(**kwargs)
# Get id from PhysicalPart instance
context['part_id'] = self.object.id
# Get category from PhysicalPart instance
context['part_category'] = self.object.category
# Add choices to form 'subcategory' field
context['form'].fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Return context to be used in form view
return context
def post(self, request, *args, **kwargs):
# Get instance of PhysicalPart
self.object = self.get_object()
# Load form
form = self.get_form()
# Add choices to form 'subcategory' field
form.fields['subcategory'].choices = SubcategoryFilter[self.object.category]
# Check if form is valid and save PhysicalPart instance
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
From my understanding you are trying to edit an instance. This is how you do it in Django, it should autopopulate your inputs with the proper values :
my_record = MyModel.objects.get(id=XXX)
form = MyModelForm(instance=my_record)
More details on this answer : how to edit model data using django forms
If your models are properly done (with relations) you shouldn't need to provide the choices for the Select.
Related
I am trying to a update a form from a separate function.
By clicking on button "Add" the existing form is updated by adding a user to the form.
Adding the user to the form works fine. However I am loosing the text input from the initial form when submitting my update.
The reason why I am using 2 functions is because I have multiple forms on the same template: each form is redirected to a specific url defined in action="{% url %}"
The usual way I use to update a form is as follows:
def function(request, id)
instance = get_object_or_404(Model, pk=id)
data = Model(request.POST or None, instance = instance)
This is not working in this case because I need to provide the instance_id on the parent function, but the id parameter is provided on the function that supports the form. (child function)
I suppose there is 2 options/questions:
Can I access the form_id from the parent function?
Should I deal with this in the form function? and in this case how do I keep the
existing text when updating form?
model
class UserProfile(models.Model):
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
class Model(models.Model):
user = models.ManyToManyField(User, blank=True)
text = models.TextField('text', blank=True)
template (main/parentfunctiontemplate.html)
{%for model in a %}
<form action="{%url 'child-function' userprofile.id model.id%}" method="POST">
{% csrf_token %}
<input type="Submit" value="Add" class="btn btn-primary custom-btn">
</form>
{%endfor%}
view
def parent_function(request, userprofile_id):
a = Model.objects.filter(venue=request.user.userprofile.venue)
updateform=ModelForm()
return render(request,"main/parentfunctiontemplate.html",{'updateform':updateform,'a':a})
def child_function(request, userprofile_id,model_id):
url = request.META.get('HTTP_REFERER')
userprofile = get_object_or_404(UserProfile, pk=userprofile_id)
instance = get_object_or_404(Model, pk=model_id)
updateform = ModelForm(request.POST or None, instance = instance)
if updateform.is_valid():
data = updateform.save(commit=False)
data.save()
updateform.save_m2m()
current_model_instance = get_object_or_404(Model, id=data.id)
current_model_instance.user.add(userprofile.id)
return redirect(url)
else:
print(updateform.errors)
return redirect('main/parentfunctiontemplate.html',{'userprofile':userprofile,'instance ':instance })
form
class ModelForm(ModelForm):
class Meta:
model = Model
fields = ('text')
this is my first Django project and I am stuck. If not a solution, but just a hint on what I am doing wrong will be truly appreciated.
I have a model with one attribute set as Null by default and I want to use a Form to update that attribute with the ID taken from another model.
These are the 2 models:
models.py
class Squad(models.Model):
squad_name = models.CharField(max_length=20)
def __str__(self):
return self.squad_name
class AvailablePlayer(models.Model):
player_name = models.CharField(max_length=25)
squad = models.ForeignKey(Squad, on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.player_name
This is the form:
forms.py
class AddSquadToPlayerForm(forms.Form):
# squad the player will be added to
squad_to_player = forms.ModelMultipleChoiceField(queryset=None)
def __init__(self, *args, **kwargs):
super(AddSquadToPlayerForm, self).__init__()
self.fields['squad_to_player'].queryset = AvailablePlayer.objects.all()
This is the view file, where I think something is missing/wrong:
views.py
def add_player_to_squad(request, squad_id):
# player = AvailablePlayer.objects.get(id=squad_id)
squad = Squad.objects.get(id=squad_id)
if request.method == "POST":
form = AddPlayerToSquadForm(request.POST)
if form.is_valid():
form.squad = form.cleaned_data['id']
form.save()
return redirect('fanta_soccer_app:squad', squad_id=squad_id)
else:
form = AddPlayerToSquadForm(queryset=AvailablePlayer.objects.all())
context = {"squad": squad, "form": form}
return render(request, 'fanta_soccer_app/add_player.html', context)
And finally, this is html file
add_player.html
<body>
<p>Add a new player to the Squad:</p>
<p>{{ squad }}</p>
<form action="{% url 'fanta_soccer_app:add_player' squad.id %}" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<button name="submit" type="submit" >Add player</button>
</form>
</body>
The rendered html shows correctly a form with a drop down menu correctly populated with the objects in the database from the "AvailablePlayer" model, however when selecting one and clicking on "Add", nothing happens, and selected player is not added to the DB.
Thank you in advance for your time.
EDIT: the code in views.py has been modified according to a comment
ADDITIONAL INFO: to confirm the db is working, if I manually add the squad_id to one of the AvailablePlayer(s) in the DB, it will be correctly listed in the squad details view.
Based off the docs I think where you are going wrong is that you are trying to update an existing value, which requires you to pass an instance keyword argument to form.save().
So I think you need to do something like this:
if form.is_valid():
a = Squad.objects.get(id=squad_id)
form = AddSquadToPlayerForm(request.POST, instance=a)
form.squad = form.cleaned_data['id']
form.save()
I managed to fix it by myself, and sharing here the solution:
I changed the form field from ModelMultipleChoiceField to ModelChoiceField. ModelMultipleChoiceField is for ManyToMany relationships model fields, while mine is a ForeignKey. I have also removed the init method:
squad_to_player = forms.ModelChoiceField(queryset=AvailablePlayer.objects.all())
The view was pretty messed up, as I was aware of, but after fixing the above, I started to have error logs, so I could eventually come up with the following solution:
view.py
def add_player_to_squad(request, squad_id):
squad = Squad.objects.get(id=squad_id)
if request.method == "POST":
form = AddPlayerToSquadForm(request.POST)
if form.is_valid():
player_to_sign = form.cleaned_data['squad_to_player']
player_to_sign.squad = squad
player_to_sign.save()
return redirect('fanta_soccer_app:squad', squad_id=squad_id)
else:
form = AddPlayerToSquadForm()
context = {"squad": squad, "form": form}
return render(request, 'fanta_soccer_app/add_player.html', context)
I am not sure what is not working correctly here. The CBV CreateView include a form but when I try to click on 'submit' in the template I receive the 'error' Method Not Allowed (POST)
forms.py
class DateInput(forms.DateInput):
input_type = 'date'
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields = ('check_in',
'check_out')
widgets = {
'check_in': DateInput(),
'check_out': DateInput()
}
class PropertyDetailView(DetailView):
model = PropertyListing
context_object_name = 'name'
template_name = 'core/property-detail.html'
def get_context_data(self, *args, **kwargs):
context = super(PropertyDetailView, self).get_context_data(**kwargs)
context['property_details'] = PropertyListing.objects.filter(pk=self.kwargs.get('pk'))
# Form
context['form'] = BookingForm()
return context
just the form
HTML
<form class="col-sm-3" role="form" action="" method="POST">
{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-primary" type="submit" value="Create" />
</form>
Does anybody have an idea why?
Like the error says, a DetailView [Django-doc] does not implement a handler for POST requests. Therefore if you make a POST request to the handler, it will produce a HTTP 405 error: Method not allowed.
We thus will need to implement a handler for a POST request ourselves. The good news is that a lot of functionality is already implemented in the ModelFormMixin [Django-doc]. We thus can implement this like:
class PropertyDetailView(ModelFormMixin, DetailView):
model = PropertyListing
context_object_name = 'name'
template_name = 'core/property-detail.html'
form_class = BookingForm
success_url = ...
def get_context_data(self, *args, **kwargs):
context = super(PropertyDetailView, self).get_context_data(**kwargs)
context['property_details'] = PropertyListing.objects.filter(pk=self.kwargs.get('pk'))
return context
def post(self, *args, **kwargs):
self.object = None
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
You thus do not need to add a Form to the context data (the ModelFormMixin will do that), nor do you need to handle the form yourself. You will however need to specify a success_url [Django-doc], or override the form_valid method [Django-doc].
That being said, it might be better to use a CreateView [Django-doc] or an UpdateView [Django-doc], and just apply some code changes to add details of your objects to it.
I want to make a comments section in my post detail page. For that i was watching a tutorial for that on youtube. Here the tutorial uses function based view and i want to make it class based view.
Can anyone please help me convert this to class based view
in function based view
def post_detail(request, slug=None):
instance = get_object_or_404(Post, slug=None)
content_type = ContentType.objects.get_for_model(Post)
obj_id = Post.id
comments = Comment.objects.filter(content_type=content_type, object_id=obj_id)
context = {
"title": instance.title,
"instance": instance,
"comments": comments,
}
return render(request, "post_detail.html", context)
so far i tried this way to make it class based which i know is wrong.
class PostDetailView(LoginRequiredMixin,DetailView):
model = Post
template_name = 'posts/post_detail.html'
content_type = ContentType.objects.get_for_model(Post)
obj_id = Post.id
comments = Comment.objects.filter(content_type=content_type, object_id=obj_id)
But this gives me error something like this
return int(value) TypeError: int() argument must be a string, a bytes-like object or a number, not 'DeferredAttribute'
There are issues in your function view as well. Looking at your class based view, if you want to display comments in your detail view, then you don't want any of those thing, all you need this,
class PostDetailView(DetailView):
model = Post
template_name = 'posts/post_detail.html'
To show comments related to the post all you need to use your related name, in your comment model you should name something like this,
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='postcomments')
# ... (other code)
To show this in html, all you need to do this,
{% for comment in post.postcomments.all %}
{{comment.text}} #add according to your model
{% endfor %}
To create comment in the same page you need to add some extra things, Make sure you have comment form as well.
class PostDetailView(DetailView):
model = Post
template_name = 'post/post_detail.html'
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
context['commentform'] = CommentForm()
return context
def post(self, request, pk):
post = get_object_or_404(Post, pk=pk) #Assuming you have <int:pk> in url, if you have slug change pk to slug
form = CommentForm(request.POST) #Add your comment form
if form.is_valid():
obj = form.save(commit=False)
obj.post = post
obj.user = self.request.user
obj.save()
return redirect('detail', post.pk) # Correct it according your urlpattern name
In your same post detail html you can simply add your same html as you have used in other form templates,
<form method="POST" action="" >
{% csrf_token %}
<p> {{form}} </p>
<button type="submit"> Create </button>
</form>
Hello I want to delete an object in a model but I don't want to show the id in the slug, so I realized I can send the data via a hidden tag in a form, but I didn't manage to make it work
Template
<form action="{% url "delete_url" %}" method="post">
{% csrf_token %}
<input type="hidden" name="pk" value={{ model.pk }}>
<button type="submit">Delete</button>
</form>
Url. Check I don't want slug
path("delete_view", views.MyDeleteView.as_view(), name="delete_url")
View
class MyDeleteView(DeleteView):
model=ModelName
success_url = reverse_lazy("success_url")
First of all, your sample code has various problems which need to be fixed:
The input type should be hidden, not hiden.
Your class based view name should have proper capitalization: MyDeleteView, not delete_view
Now, for your actual problem: The DeleteView uses SingleObjectMixin to identify the object (check this out https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/DeleteView/). Thus, you need to override the get_object method of SingleObjectMixin so as to use the POST data instead of the slug to retrieve the object. Probably something like this should work (warning there's no error handling):
class MyDeleteView(DeleteView):
model=ModelName
success_url = reverse_lazy("success_url")
def get_object(self, queryset=None):
pk = self.request.POST['pk']
return self.get_queryset().filter(pk=pk).get()
For a gentle introduction to CBV I recommend my CBV guide: https://spapas.github.io/2018/03/19/comprehensive-django-cbv-guide/
Thanks to Serafeim I can find the way to do it, I have to replace the get_object() method and copy some code from the original method. I get the code from the link:
https://ccbv.co.uk/projects/Django/2.0/django.views.generic.edit/DeleteView/
Finaly:
class MyDeleteView(DeleteView):
model=ModelName
success_url = reverse_lazy("success_url")
def get_object(self, queryset=None):
pk = self.request.POST['pk']
if queryset is None:
queryset = self.get_queryset()
if pk is not None:
queryset = queryset.filter(pk=pk)
try:
# Get the single item from the filtered queryset
obj = queryset.get()
except queryset.model.DoesNotExist:
raise Http404("No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj