Django UpdateView - no object due to POST call - python

I have two UpdateViews, one works and the other doesn't... Please see Update IV
The working model is:
views.py
class JuryUpdate(UpdateView):
model = Jury
fields = [
'jury_name',
]
template_name_suffix = '_update_form'
def get_object(self, *args, **kwargs):
return get_object_or_404(Jury, jury_id=self.kwargs['jr'])
def form_valid(self, form):
form.instance.customer_id = self.kwargs['pk']
form.instance.court_year = self.kwargs['yr']
form.instance.jury_id = self.kwargs['jr']
return super(JuryUpdate, self).form_valid(form)
templates/jury_update_form.html (in relevant part)
<div class="container">
<h5>Update {{for.instance.jury_name}}</h5>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save" />
</form>
</div>
This setup will render an updateview with the object labels and existing field data from the object. This next setup doesn't work...
views.py
class CustomerUpdate(UpdateView):
model = Customer
fields = [
'customer',
]
template_name_suffix = '_update_form'
def get_object(self, *args, **kwargs):
return get_object_or_404(Customer, customer_id=self.kwargs['pk'])
def form_valid(self, form):
form.instance.customer_id = self.kwargs['pk']
return super(CustomerUpdate, self).form_valid(form)
templates/customer_update_form.html (in relevant part)
<div class="container">
<h5>Update {{form.instance.customer}}</h5>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save" />
</form>
</div>
The second updateview does provide an update form but it doesn't populate with the calling object's data. It would seem that the object is there since the {{form.instance.customer}} contains the correct customer data for the call (no different than the JuryUpdate view).
I've tried to explicitly call get_initial (as described here) and print, but the result is {}. I've also tried variation of the form_valid call but as presented above, I believe I'm getting the correct object. There are several examples (such as here) that use get_initial to pre-populate with existing information - but that doesn't work in this instance and it isn't needed in my JuryUpdate view.
Any help is appreciated.
UPDATE I
models.py
class Customer(models.Model):
class Meta:
verbose_name = "Customer"
verbose_name_plural = "Customers"
customer_id = models.AutoField(
primary_key=True)
customer = models.CharField(
max_length=40)
# table fields
def get_absolute_url(self):
return reverse(
'customer-detail-view',
kwargs={'pk':self.pk})
def __str__(self):
return self.customer
class Jury(models.Model):
class Meta:
verbose_name = "Jury"
verbose_name_plural = "Juries"
customer = models.ForeignKey(
Customer,
on_delete=models.CASCADE)
court_year = models.ForeignKey(
CourtYear,
on_delete=models.CASCADE)
jury_id = models.AutoField(
primary_key=True)
jury_name = models.CharField(
max_length=20)
# other table fields
def get_absolute_url(self):
return reverse(
'jury-list-view',
kwargs={'pk':self.customer_id, 'yr':self.court_year_id})
def __str__(self):
return self.jury_name
urls.py
path('add_customer/', views.CustomerCreate.as_view(), name='customer-add'),
path('<int:pk>/', views.CustomerDetailView.as_view(), name='customer-detail-view'),
path('<int:pk>/delete/', views.CustomerDelete.as_view(), name='customer-delete'),
path('<int:pk>/update/', views.CustomerUpdate.as_view(), name='customer-update'),
path('<int:pk>/<int:yr>/', views.JuryListView.as_view(), name='jury-list-view'),
path('<int:pk>/<int:yr>/add_jury/', views.JuryCreate.as_view(), name='jury-add'),
path('<int:pk>/<int:yr>/<int:jr>/updatejury', views.JuryUpdate.as_view(), name='jury-update'),
path('<int:pk>/<int:yr>/<int:jr>/deletejury', views.JuryDelete.as_view(), name='jury-delete'),
UPDATE II
I've added a get_initial() method to my CustomerUpdate(UpdateView) as follows:
def get_initial(self):
initial = super(CustomerUpdate, self).get_initial()
print('initial data', initial)
customer_object = self.get_object()
initial['customer'] = customer_object.customer
# other fields omitted...
print('initial data updated', initial)
return initial
The initial data print returns {}. The initial data updated print returns {'customer': 'John Doe'} (plus the "other fields"). So it seems that the right information is getting pulled and delivered - It must be in the html?
Update III
I've taken the CustomerUpdate(UpdateView) down to the very basic class model:
class CustomerUpdate(UpdateView):
model = Customer
fields = [
'customer',
]
template_name_suffix = '_update_form'
The template is already the basic format (docs) - the rendered webpage still doesn't have object data for updating...
Update IV
I think I've figured out the problem - but don't know how to fix...
When I use the JuryUpdate call the console shows:
[02/Jun/2018 16:19:19] "GET /myapp/1/3/9/updatejury/?csrfmiddlewaretoken=1kHK4xgqdbBfXsv6mtz0WKgKpewFwLVtpUX5Z51qnLsGaMDVmpdVHKslXAXPhvY8 HTTP/1.1" 200 3687
When I use the CustomerUpdate call the console shows:
[02/Jun/2018 16:18:57] "POST /myapp/5/update/ HTTP/1.1" 200 3354
So my updateview on the Jury update is a GET call while my udpateview on Customer is aPOST call. In looking through the docs, I can see that the GET call with show the data while the POST call is (I think) assuming a black data set. I can't figure out why I'm getting a different result - where would this be set/changed?

After 3 days - I traced the issue - it had nothing to do with the view, model, url or the update template. The offending code was actually attached to the update button. Very specifically the page that had the button for "Update" used the following <form> code:
<form action="{% url 'customer-update' pk=customer.client_id %}" method="post" style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-outline-primary btn-custom-xs">U</button>
</form>
In the form call the method used was "POST" - and although I don't exactly understand the intracacies, the result is a blank UpdateView. The following code in the calling page fixed the problem.
<form action="{% url 'customer-update' pk=customer.client_id %}" style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-outline-primary btn-custom-xs">U</button>
</form>

Related

Django Handling Multiple Image Uploads

I have a simple project that has two different models. One that handles a report and one that stores the images for each report connected by a ForeignKey:
class Report(models.Model):
report_location = models.ForeignKey(Locations, on_delete=models.CASCADE)
timesheet = models.ImageField(upload_to='report_images', default='default.png')
text = models.CharField(max_length=999)
report_date = models.DateField(auto_now=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.report_location, self.report_date, self.created_by}"
class TimeSheetAndPics(models.Model):
report = models.ForeignKey(Report, on_delete=models.CASCADE)
report_images = models.ImageField(upload_to='report_images', default='default.png')
date = models.DateField(auto_now=True)
def __str__(self):
return f"{self.report} on {self.date}"
My Goal is to have a user fill out the report and then upload multiple pictures, however i cannot figure out how to handle multiple image uploads.
I have two forms for each model:
class ReportCreationForm(ModelForm):
class Meta:
model = Report
fields = [
'report_location',
'text',
]
class TimeSheetAndPicForm(ModelForm):
report_images = forms.FileField(widget=ClearableFileInput(attrs={'multiple': True}))
class Meta:
model = TimeSheetAndPics
fields = [
'report_images',
]
And this is how i try to handle my views:
class NewReport(LoginRequiredMixin, View):
def get(self, request):
context = {
'create_form': ReportCreationForm(),
'image_form': TimeSheetAndPicForm(),
}
return render(request, 'rakan/report_form.html', context)
def post(self, request):
post = request.POST
data = request.FILES or None
create_form = ReportCreationForm(post)
image_form = TimeSheetAndPicForm(post, data)
if create_form.is_valid() and image_form.is_valid():
clean_form = create_form.save(commit=False)
clean_form.created_by = request.user
clean_form.save()
clean_image_form = image_form.save(commit=False)
for images in clean_image_form:
clean_image_form.report = clean_form
clean_image_form.report = images
clean_image_form.save()
return redirect('rakan:rakan_index')
return redirect('rakan:new-report')
I have tried to solve this in different ways but i unfortunately hit a wall. I cant seem to find a solution that actually works. My best try i was able to save only 1 image in the models instead of the 3 test images.
I dont believe it makes a difference but here is also the HTML File that uses the forms:
<div class="content-section">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Create Report</legend>
{{ create_form }}
<br>
{{ image_form }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">submit</button>
</div>
</form>
</div>
Anyone dealt with this problem would like to help me achieve a solution i would be very thankful. Thank you.

How to get template variables by using FormView?

I am currently following Mozilla's Django tutorial (https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms). The tutorial mostly shows how to create form using functions. I am trying to make the same function view work by using a generic class view (FormView). I am able to make most of the code to work except for 2 things. First one is that I can't seem to be able to save the due date. And, second one, is that I don't know how to access the model fields in my template using template variables.
Here is my form model from the forms.py file.
class RenewBookModelForm(ModelForm):
def clean_due_back(self):
data = self.cleaned_data['due_back']
# Check if a date is not in the past.
if data < datetime.date.today():
raise ValidationError(ugettext_lazy(
'Invalid date - renewal in past'))
# Check if a date is in the allowed range (+4 weeks from today).
if data > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(ugettext_lazy(
'Invalid date - renewal more than 4 weeks ahead'))
# Remember to always return the cleaned data.
return data
class Meta:
model = BookInstance
fields = ['due_back']
labels = {'due_back': ugettext_lazy('New renewal date')}
help_texts = {'due_back': ugettext_lazy(
'Enter a date between now and 4 weeks (default 3).')}
The form model implemented as a function:
#permission_required('catalog.can_mark_returned')
def renew_book_lirarian(request, pk):
book_instance = get_object_or_404(BookInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = RenewBookModelForm(request.POST)
# Chech if the form is valid
if form.is_valid():
# process that data in form.cleaned_data as required (here we just write to the model due_back field)
book_instance.due_back = form.cleaned_data['due_back']
book_instance.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('all-borrowed'))
# If this is a GET (or any other method) create the default form.
else:
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = RenewBookModelForm(initial={'due_back': proposed_renewal_date})
context = {
'form': form,
'book_instance': book_instance
}
return render(request, 'catalog/book_renew_librarian.html', context=context)
This is my class-based view from my views.py file:
class RenewBookLibrarian(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
"""Generic class-based view for forms."""
template_name = 'catalog/book_renew_librarian.html'
permission_required = 'catalog.can_mark_returned'
form_class = RenewBookModelForm
success_url = reverse_lazy('all-borrowed')
def get_initial(self):
proposed_renewal_date = datetime.date.today() + datetime.timedelta(weeks=3)
initial = {'due_back': proposed_renewal_date}
return initial
And finally this is my template file where I wish to access the model fields:
{% extends 'base_generic.html' %}
{% block content %}
<h1>Renew: {{ book_instance.book.title }}</h1>
<p>Borrower: {{ book_instance.borrower }}</p>
<p {% if book_instance.is_overdue %} class="text-danger" {% endif %}>Due date: {{ book_instance.due_back }}</p>
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="submit">
</form>
{% endblock %}
The book_instance variable in the template is not working, hence I would like to know how I can display fields from my BookInstance model.
To add book_instance to the template context, you can override get_context_data.
In the FormView, instead of checking if form.is_valid(), you override the form_valid method (see the class based view docs for basic forms).
class RenewBookLibrarian(LoginRequiredMixin, PermissionRequiredMixin, generic.FormView):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['book_instance'] = get_object_or_404(BookInstance, pk=self.kwargs['pk'])
def form_valid(self, form):
book_instance = get_object_or_404(BookInstance, pk=self.kwargs['pk'])
book_instance.due_date = form.cleaned_data['due_date']
book_instance.save()
return super().form_valid(form) # will redirect to the success url

How set initial values to fields on form?

enter code hereI'm having some problems to solve a problem. I have a template, which allows the user to change some of their account settings. My goal is to initialize the form, with the user's default values, and he can keep or change them (by after submit form). However, until now the page does not render these values. I'm using a class based view, CreateView, for this purpose.
My code is listed below.
Here, is my CreateView.
class DetailUserInfoView(LoginRequiredMixin ,CreateView):
model = CustomUser.CustomUser
template_name = 'users/InfoUser.html'
login_url = settings.LOGOUT_REDIRECT_URL
context_object_name = 'user'
form_class = CustomUserChangeForm
def get_object(self):
self.model = self.request.user
return self.model
def get_initial(self):
initial = super(DetailUserInfoView, self).get_initial()
initial = initial.copy()
initial[config.USERNAME] = self.request.user.username
initial[config.FIRST_NAME] = self.request.user.first_name
initial[config.LAST_NAME] = self.request.user.last_name
return initial
def get_form_kwargs(self):
kwargs = {'initial': self.get_initial()}
return kwargs
def get_context_data(self, **kwargs): #GET OBJECT ACTS AFTER THAN GET_OBJECT --> EXAMPLE OF GET_CONTEXT_DATA, I DIDN'T NEED THIS
context = super(DetailUserInfoView, self).get_context_data(**kwargs)
context['username'] = self.request.user.username
return context
Here the form.
class CustomUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = CustomUser.CustomUser
fields = ('email', 'password', 'first_name', 'last_name', 'username', 'userType')
And finally an extract of template.
<div id="infoMayOverride">
<form class="getOverridedValues" method="post">
{% csrf_token %}
<div id="usernameData">
<label>{{ form.username.label_tag }}</label> <!--MODEL ON CREATEUSERVIEW IS CUSTOMUSER, AND NOW I NEED TO USE THIS FIELDS AND INHERITED FIELDS FROM USER CLASS-->
<input type="text" id="usernameInput" value="{{ form.username }}">
</div>
<div id="firstNameData">
<label>{{ form.first_name.label_tag }}</label>
<input type="text" id="firstNameInput" value="{{ form.first_name }}">
</div>
<div id="lastNameData">
<label>{{ form.last_name.label_tag }}</label>
<input type="text" id="lastNameInput" value="{{ form.last_name }}">
</div>
<div id="divBtnChangeProfile">
<input type="submit" class="btnChangeProfile" value="Atualizar Profile">
</div>
</form>
</div>
I'd appreciate it if you could help me. I am new to the Django environment, and have tried many approaches, and I have not yet been able to solve this problem.
--------------------------- Update ------------------------------------
Now, i can get initial values. But to view them i need to write on input form: form.username.initial, and with this i can't after submit form to update user values.
Anyone knows how to solve this type of problem??
I finally got this problem solved. I will make my solution available, since it can help other people.
I had to make some changes to the code I provided behind.
Below is the code of view.
class DetailUserInfoView(LoginRequiredMixin, UpdateView):
model = CustomUser.CustomUser
template_name = 'users/InfoUser.html'
login_url = settings.LOGOUT_REDIRECT_URL
context_object_name = 'user'
form_class = CustomUserChangeForm
def get_object(self, queryset=None):
return self.request.user
def get_form_kwargs(self):
kwargs = super(DetailUserInfoView, self).get_form_kwargs()
u = self.request.user
kwargs['username_initial'] = u.username
kwargs['fName_initial'] = u.first_name
kwargs['lName_initial'] = u.last_name
return kwargs
def get_context_data(self, **kwargs): #GET OBJECT ACTS AFTER THAN GET_OBJECT --> EXAMPLE OF GET_CONTEXT_DATA, I DIDN'T NEED THIS
context = super(DetailUserInfoView, self).get_context_data(**kwargs)
form_class = self.get_form_class()
form = self.get_form(form_class)
context['form'] = form
return context
My form (with init function, to set initial values on form, and is called by def get_form_kwargs(self)).
class CustomUserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs):
username_initial = kwargs.pop('username_initial', None)
fName_initial = kwargs.pop('fName_initial', None)
lName_initial = kwargs.pop('lName_initial', None)
super(CustomUserChangeForm, self).__init__(*args, **kwargs)
self.fields['username'].initial = username_initial
self.fields['first_name'].initial = fName_initial
self.fields['last_name'].initial = lName_initial
class Meta(UserChangeForm.Meta):
model = CustomUser.CustomUser
fields = ('username', 'email', 'first_name', 'last_name')
And finnaly, in template I replace the tag input with {{ form.username }}.
I hope it can help someone who has the same problem.

Pre-populating a child models django create form with a parent's ID

I have followed the guidelines from This answer in order to pass Parent pk to the child creation page. At the moment though it is not working and I am seeing the following log.
[14/Jul/2017 13:15:37] "POST /catalog/productstatus/2/create/ HTTP/1.1" 200 4001
I'm not sure what I'm doing wrong, here is the code I currently have.
Models
Models.py
class Product(models.Model):
serial_number = models.CharField(unique=True, max_length=15)
class ProductStatus(models.Model):
serial_number = models.ForeignKey('Product', on_delete=models.CASCADE, null=True)
status = models.CharField(max_length=20, blank=True, default='Stock', help_text='Products status')
date = models.DateTimeField(auto_now_add=True)
View
class ProductStatusCreate(CreateView):
model = ProductStatus
template_name = 'catalog/productstatus_create.html'
form_class = ProductStatusModelForm
def form_valid(self, form):
productstatus = form.save(commit=False)
product_id = form.data['product_id']
product = get_object_or_404(Product, id=product_id)
productstatus.product = product
return super(ProductStatusCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(ProductStatusCreate, self).get_context_data(**kwargs)
context['s_id'] = self.kwargs['product_id']
return context
def get_success_url(self):
if 'product_id' in self.kwargs:
product = self.kwargs['product_id']
else:
product = self.object.product.pk
return reverse_lazy('product_detail', kwargs={'pk': product})
Forms
class ProductStatusModelForm(forms.ModelForm):
class Meta:
model = ProductStatus
fields = ['status',]
def __init__(self, *args, **kwargs):
self.fields["product"] = forms.CharField(widget=forms.HiddenInput())
super(ProductStatusModelForm, self).__init__( *args, **kwargs)
templates/myapp/product_detail.html
New
urls.py
urlpatterns += [
url(r'^productstatus/(?P<product_id>\d+)/create/$', views.ProductStatusCreate.as_view(), name='productstatus_create'),
]
productstatus_create.html
{% extends "base_generic.html" %}
{% block content %}
<h2>New Product Status</h2>
</br>
<form action="" method="post">
{% csrf_token %}
<table>
<input type=hidden id="id_product" name="product" value="{{ s_id }}">
{{ form }}
</table>
<input type="submit" value="Submit" />
</form>
</br>
{% endblock %}
When looking at the page's source the value does get populated but when I submit the form nothing happens.
Why do you have views.ProductInstanceCreate.as_view() in your urls.py but the view you show is called ProductStatusCreate? Are you sure you are using the right view?
You are creating a 'product' hidden field in your form, but not providing a value for it anywhere. Your template output then has two product fields, and the latter (blank) is taken, so returns an error saying it is required.
None of this outputting the product ID to the template in order to read it back in is necessary - you always have the ID available to you in the URL kwargs.
You can get rid of your get_context_data, and the extra field code in the Form and template. Your form_valid can be something like:
def form_valid(self, form):
product = get_object_or_404(Product, id=self.kwargs['product_id'])
form.instance.product = product
return super().form_valid(form)
And product_id will always be in self.kwargs, so your get_success_url can be shorter too:
def get_success_url(self):
product = self.kwargs['product_id']
return reverse('product_detail', kwargs={'pk': product})

Getting a 'This field is required` error when updating a model instance

I have an edit view for when a user wants to edit a Post:
def edit(request, id):
post = get_object_or_404(Post, id=id)
edit_form = PostForm(request.POST or None, instance=post)
if edit_form.is_valid():
instance = edit_form.save(commit=False)
instance.save(update_fields=['content'])
return HttpResponseRedirect('/')
else:
print(edit_form.errors)
edit_form = PostForm(instance=post)
context = {
'edit_form': edit_form,
'form_post': post
}
return render(request, 'edit.html', context)
When a user edits a Post, I only want them to be able to edit 1 field (content), so i've only rendered that form field in my template (pre-populated with the previous post.content. The other fields are just fields of the object (not a form/can't be edited).
...
<form method="post" action="" enctype="multipart/form-data">{% csrf_token %}
<h1>{{ form_post.title }}</h1>
<p>{{ edit_form.content}}</p>
<p>{{ form_post.category }}</p>
</form>
...
and here is my Post model:
class Post(models.Model):
...
title = models.TextField(max_length=76)
content = models.TextField(null=False, default='')
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default='1')
When the edit form is submitted, form_errors returns this:
<ul class="errorlist">
<li>title<ul class="errorlist"><li>This field is required.</li></ul></li>
<li>category<ul class="errorlist"><li>This field is required.</li></ul</li>
</ul>
Why is this happening? Doesn't:
instance = edit_form.save(commit=False)
instance.save(update_fields=['content'])
keep the fields from the orignal Post and just change the content field?
If you only want some of the fields to be editable, you should set fields in your model form. If you use PostForm in another view and cannot edit fields, then create a new form.
class EditPostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['content']
You can subclass PostForm if you prefer:
class EditPostForm(PostForm):
class Meta(PostForm.Meta):
fields = ['content']
Then update your edit view to use EditPostForm instead of PostForm.

Categories