Data from POST is being ignored by Django - django

I'm having trouble with a form related issue where the data from a POST is clearly being sent back, but Django is stripping it out so that it won't go into my form class.
Here are some code snippets from my class's post() that show the data is being returned from the template and then how the data is not making it through the form class filtering.
Why is this happening?
What is the harm with me taking the data directly from the POST and not using the built-in form class filtering?
print(request.POST)
Yields this:
<QueryDict: {'book': ['Zippity do dah,'], 'weather': ["It's raining!"], 'dividend': ['Zippity aye!'], 'earnings': ['Yo man, one step at a time.'], 'csrfmiddlewaretoken': ['3MWFOwebVeYfCum8JaGvITRB542b3jbp']}>
and
main_form = self.main_form_class(request.POST)
print('\n','main_form: ',main_form)
Yields this:
main_form: <tr><th><label for="id_weather">weather:</label></th><td><input id="id_weather" maxlength="256" name="weather" size="36" type="text" /></td></tr>
<tr><th><label for="id_book">book:</label></th><td><input id="id_book" maxlength="256" name="book" size="36" type="text" /></td></tr>
<tr><th><label for="id_dividend">dividend:</label></th><td><input id="id_dividend" maxlength="256" name="dividend" size="36" type="text" /></td></tr>
<tr><th><label for="id_earnings">earnings:</label></th><td><input id="id_earnings" maxlength="256" name="earnings" size="36" type="text" /></td></tr>
<tr><th><label for="id_csrfmiddlewaretoken">csrfmiddlewaretoken:</label></th><td><input id="id_csrfmiddlewaretoken" maxlength="256" name="csrfmiddlewaretoken" size="36" type="text" /></td></tr>
You can see that no 'values' are being included in main_form.
main_form.is_valid() is never True
main_form.cleaned_data throws an error - if I pull it out in front of main_form.is_valid()
Help!
OK - here is some more of the code:
I have a sub-class that has a form that is not returning data from a POST request. The super-class also has a form and this IS returning data from the same POST request. That is to say that the super-class is requesting user input and there are several sub-classes that extend the super-class and request other data. The super-class always returns the data from it's part of the form, but the subclasses do not.
The subclasses return data in the POST, but the data doesn't make it back to the Django view. This seems like a bug, (but perhaps it's a feature) or another likely scenario is that I'm missing something. However, I cannot find it.
A solution might be to have no super-class and just have a very non-DRY program. Yuck.
I've put in a few print() to terminal commands to look at what is coming back (or not coming back) to verify this.
I have also used my browser's Developer Tools to see that the POST does, in fact, have the necessary data in it that should be returned to the view for processing. However, this data does not make it back to the view.
The sub-class view code looks like this:
class MainView(SidebarBaseView):
main_form_class = MainViewForm
template_name = 'myapp/my_view.html'
def get(self, request, *args, **kwargs):
context = super(MainView, self).get(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form_initial = {}
for detail in context['my_data']: # my_data comes from get_context_data()
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
context = super(MainView, self).post(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form = self.main_form_class(request.POST)
print('\n','main_form',main_form,'\n') #to terminal
if main_form.is_valid():
main_form_data = main_form.cleaned_data
print('\n', 'cleaned data', main_form_data, '\n') #to terminal
for detail in context['my_data']:
field_name = detail.datatype.name
detail.note = main_form_data[field_name]
detail.save()
main_form_initial = {}
for detail in context['my_data']:
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def get_context_data(self, *args, **kwargs):
context = super(CompanyStatsView, self).get_context_data(**kwargs)
...
return context
My form make dynamic fields since I need to send it a variable number of fields eventually:
class MyMainViewForm(forms.Form):
fields = {}
field_names = ['weather','book','dividend','earnings']
def __init__(self, field_names, *args, **kwargs):
super(MyMainViewForm, self).__init__(*args, **kwargs)
for name in field_names:
self.fields[name] = forms.CharField(label=name, max_length=256, required=False, widget=forms.TextInput(attrs={'size':'36'}))
My template looks like this:
{% extends 'my_sidebar.html' %}
{% block content_right %}
<div class="container-fluid">
<h2>Table</h2>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
{% for detail in my_data %}
<tr height="50">
<td>{{ detail.datatype.full_name }}:</td>
<td class="text-right">{{ detail }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
<form action="." method="post"> {% csrf_token %}
{% for note in main_form %}
<tr height="50">
<td>{{ note }}</td>
</tr>
{% endfor %}
<p><input type="submit" value="Save"></p>
</form>
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
Any ideas?
The pertinent part of urls.py is:
from django.conf.urls import url
from . import views
app_name = 'my_app'
urlpatterns = [
url(r'^(?P<slug>[A-Z]+)/data/$', views.MainView.as_view(), name='main-view',),
...
]

Related

How to search for exact values in Django?

I have created a search function. However, it searches for all elements that contain the entered value. For example, there are the following elements: 44564, 76436, 445. When I enter "445", it shows "44564" and "445", but I need only 445. Or if I enter "64", then nothing should be shown, but "44564" and "76436" are shown. How to fix it?
case_list.html
<div>
<h3>Search</h3>
<form method="GET" action="{% url 'case_search' %}">
<input type="search" type="text" name="q" prequired placeholder="Put value">
<button type="submit">Find</button>
</form>
</div>
<div>
{% for case in object_list %}
<div>
<p>{{ case.name }}</p>
</div>
{% endfor %}
</div>
Views.py
class CaseView(ListView):
model = Case
template_name = 'case_list.html'
class CaseSearch(ListView):
template_name = 'case_list.html'
def get_queryset(self):
return Case.objects.filter(name__icontains=self.request.GET.get("q"))
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context["q"] = self.request.GET.get("q")
return context
Urls.py
path('case_list/', CaseView.as_view(), name='case_list'),
path('case_list/search/', CaseSearch.as_view(), name="case_search"),
Use iexact for exact matches. Check out the docs https://docs.djangoproject.com/en/4.1/ref/models/querysets/#iexact
Case.objects.filter(name__iexact=self.request.GET.get("q"))

Django: Access context data in POST and GET

I am very new to Django and I cannot work out how to access context data within a POST request so I don't have to repeat myself.
I believe that POST runs before get_context_data, but again unsure of what exactly to do here.
The page displays some data using a default of 30 days.
Then on the page there is a form to override that value, which is passed back to the POST method to then re-render the page.
Example of page.
views.py
class ProducerDetailView3(generic.DetailView):
model = Producer
template_name = 'producer_detail3.html'
def get_queryset(self, **kwargs):
#-date_check means to descending order based on that column
return Producer.objects.filter(owner_name__exact=self.kwargs['pk'],metasnapshot_date=date(1980, 1, 1))
# Update context with more information
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super().get_context_data(**kwargs)
# Cannot update context here as you are accessing existing producer object context data.
# Create any data and add it to the context
how_many_days = self.kwargs['days'] # get DAYS from URL. KWARGS are passed in from the get request
context['day_filter'] = reverse('results:producer_detail', args=[self.kwargs['pk'], '300'])
context['results'] = Results.objects.all().filter(owner_name__exact=self.kwargs['pk']).order_by('-date_check')
context['chains_json_count'] = Results.objects.filter(owner_name__exact=self.kwargs['pk'],chains_json=True,date_check__gte=datetime.now()-timedelta(days=how_many_days)).count()
return context
def post(self, request, **kwargs):
day_filter = int(request.POST.get('day_filter'))
producer = Producer.objects.get(owner_name__exact=kwargs['pk'],metasnapshot_date=date(1980, 1, 1))
# Using reverse we create a URL to set the day filter to 300 and create a link
results = Results.objects.all().filter(owner_name__exact=kwargs['pk']).order_by('-date_check')
chains_json_count = Results.objects.filter(owner_name__exact=kwargs['pk'],chains_json=True,date_check__gte=datetime.now()-timedelta(days=day_filter)).count()
context = {
'producer': producer,
'results': results,
'chains_json_count': chains_json_count,
'day_filter_url': day_filter
}
return render(request, 'producer_detail3.html', context)
producer_detail3.html
<h1>{{ producer.owner_name }}</h1>
<div class="row">
<div class="col-md-8">
<img src="{{ producer.logo_svg }}" alt="" width="100%">
</div>
<div class="col-md-4">
<h5>Producer:owner_name{{ producer.owner_name }}</h5>
<h5>Producer.pk: {{ producer.pk }}</h5>
<!--a href="{{ day_filter_url }}"
class="btn btn-primary">
Set day filter to 300
</a>
<h5>{{ producer.url }}</h5-->
<form method="post">
<p><label for="day_filter">Set day filter value</label>
{% csrf_token %}
<input type="text" name="day_filter" size="40" id="day_filter"/></p>
<input type="submit"/>
</form>
<br>
<h5>Results: {{ chains_json_count }}</h5>
<table>
<tr>
<th>HTTP</th>
<th>Hyperion</th>
<th>Date</th>
</tr>
{% for result in results %}
<tr>
<td>{{result.http_check}}</td>
<td>{{result.hyperion_v2}}</td>
<td>{{result.date_check}}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
UPDATE:
I tried accessing the context via the POST method using:
def post(self, request, **kwargs):
context = self.get_context_data(**kwargs)
However I get the following error:
Request Method: POST
Request URL: http://127.0.0.1:8000/producers/dapplica/30
Django Version: 3.2.12
Exception Type: AttributeError
Exception Value:
'ProducerDetailView3' object has no attribute 'object'
Exception Location: /Users/user/project/django-larn/lib/python3.7/site-packages/django/views/generic/detail.py, line 94, in get_context_data
there is no "hidden" call to get_context_data - you need to populate the context by calling the method from your post() method after defining self.object like
def post (....):
....
# define the singel object to display
self.object = self.get_object()
# define the context
context = self.get_context_data(**kwargs)
# render
return self.render_to_response(context)
if necessary add additional items to context in post()

Having an edit form and a detail view on the same page

I am currently trying to get an edit form working on the same page as a detail view in Django.
I am currently trying out the way as recommended on the docs (i.e. using FormMixin). So, my view looks like this:
class ProductiveMinutesDetail(FormMixin, DetailView):
model = models.ProductiveMinutes
pk_url_kwarg = 'productiveminutes_pk'
form_class = forms.EditForm
def get_success_url(self):
return reverse_lazy('productiveminutes_list')
def get_context_data(self, **kwargs):
context = super(ProductiveMinutesDetail, self).get_context_data(**kwargs)
context['form'] = forms.EditForm(initial={'post': self.object})
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
form.save()
return super(ProductiveMinutesDetail, self).form_valid(form)
And my form looks like this:
from django import forms
from . import models
class EditForm(forms.ModelForm):
class Meta:
model = models.ProductiveMinutes
fields = ('name', 'description',)
The model I am using is this:
class Scenario(models.Model):
name = models.CharField(max_length=200)
class ProductiveMinutes(models.Model):
scenario = models.OneToOneField(Scenario)
name = models.CharField(max_length=200)
amount = models.DecimalField(max_digits=50, decimal_places=2)
description = models.CharField(max_length=200)
Using this I can get the form to render on the page but I know I am doing something wrong as the fields are empty when I would like them to be populated with the data that is already present.
Another piece of complexity is that this form should not be editing the amount field of the model just the name and description. The amount value is edited separately from the detail view of this page.
So, I guess my main question is how can I get the form to be populated with the data for the models fields that is already present and then edit it. Ideally functionality like that of the generic UpdateView that Django provides.
I am using Django version 1.10
Any help with this would be much appreciated
Thanks for your time
UPDATE:
My template looks like this:
{% extends 'pages/dashboard.html' %}
{% load i18n humanize crispy_forms_tags %}
{% block content %}
<div>
<h1 class="text-center">Productive Minutes: {{ productiveminutes.name }}</h1>
<div class="row">
<div class="col"></div>
<div class="col-md-8 col-lg-8">
<h3>Edit productive minutes: {{ productiveminutes.name }}</h3>
<form role="form" method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="primaryAction btn btn-primary pull-right" type="submit">{% trans "Submit" %}</button>
</form>
</div>
<div class="col"></div>
</div>
<hr>
<div class="row">
<div class="col"></div>
<div class="col-md-8 col-lg-8">
<h3>Data Records</h3>
<div class="table-responsive">
<table class="table table-bordered">
<thead class="thead-default">
<tr>
<th>ID</th>
<th>Productive Minutes</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ productiveminutes.id }}</td>
<td>{{ productiveminutes.amount|intcomma }}</td>
<td>
<i class="fa fa-pencil"></i>
<i class="fa fa-trash"></i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col"></div>
</div>
</div>
{% endblock content %}
The example in the docs is for displaying detail for an object and having a separate contact form.
In your case, it sound like you want to display the ProductiveMinutesDetail object, and have a form that allows you to update some of the fields of that same object. In that case, you should just use UpdateView.
class ProductiveMinutesUpdate(UpdateView):
model = models.ProductiveMinutes
pk_url_kwarg = 'productiveminutes_pk'
form_class = forms.EditForm
success_url = reverse('productiveminutes_list')
def get_context_data(self, **kwargs):
context = super(ProductiveMinutesUpdate, self).get_context_data(**kwargs)
# Refresh the object from the database in case the form validation changed it
object = self.get_object()
context['object'] = context['productiveminutes'] = object
return context

Sub-Classed View with Form Not Returning Form Data

I have a situation that I cannot reconcile. I have a sub-class that has a form that is not returning data from a POST request. The super-class also has a form and this IS returning data from the same POST request. That is to say that the super-class is requesting user input and there are several sub-classes that extend the super-class and request other data. The super-class always returns the data from it's part of the form, but the subclasses do not.
The subclasses return data in the POST, but the data doesn't make it back to the Django view. This seems like a bug, (but perhaps it's a feature) or another likely scenario is that I'm missing something. However, I cannot find it. Help!
A solution might be to have no super-class and just have a very non-DRY program. Yuck.
I've put in a few print() to terminal commands to look at what is coming back (or not coming back) to verify this.
I have also used my browser's Developer Tools to see that the POST does, in fact, have the necessary data in it that should be returned to the view for processing. However, this data does not make it back to the view.
The sub-class view code looks like this:
class MainView(SidebarBaseView):
main_form_class = MainViewForm
template_name = 'myapp/my_view.html'
def get(self, request, *args, **kwargs):
context = super(MainView, self).get(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form_initial = {}
for detail in context['my_data']: # my_data comes from get_context_data()
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
context = super(MainView, self).post(request, *args, **kwargs)
context.update(self.get_context_data(*args, **kwargs))
main_form = self.main_form_class(request.POST)
print('\n','main_form',main_form,'\n') #to terminal
if main_form.is_valid():
main_form_data = main_form.cleaned_data
print('\n', 'cleaned data', main_form_data, '\n') #to terminal
for detail in context['my_data']:
field_name = detail.datatype.name
detail.note = main_form_data[field_name]
detail.save()
main_form_initial = {}
for detail in context['my_data']:
field_name = detail.datatype.name
main_form_initial[field_name] = detail.note
main_form = self.main_form_class(initial=main_form_initial)
context['main_form'] = main_form
return render(request, self.template_name, context)
def get_context_data(self, *args, **kwargs):
context = super(CompanyStatsView, self).get_context_data(**kwargs)
...
return context
My form make dynamic fields since I need to send it a variable number of fields eventually:
class MyMainViewForm(forms.Form):
fields = {}
field_names = ['one','two','three','many']
def __init__(self, field_names, *args, **kwargs):
super(MyMainViewForm, self).__init__(*args, **kwargs)
for name in field_names:
self.fields[name] = forms.CharField(label=name, max_length=256, required=False, widget=forms.TextInput(attrs={'size':'36'}))
My template looks like this:
{% extends 'my_sidebar.html' %}
{% block content_right %}
<div class="container-fluid">
<h2>Table</h2>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
{% for detail in my_data %}
<tr height="50">
<td>{{ detail.datatype.full_name }}:</td>
<td class="text-right">{{ detail }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
<div class="table-responsive">
<table class="table">
<tbody>
<form action="." method="post"> {% csrf_token %}
{% for note in main_form %}
<tr height="50">
<td>{{ note }}</td>
</tr>
{% endfor %}
<p><input type="submit" value="Save"></p>
</form>
</tbody>
</table>
</div>
</div>
<div class="col-sm-4">
</div>
</div>
{% endblock %}
Any ideas?
The pertinent part of urls.py is:
from django.conf.urls import url
from . import views
app_name = 'my_app'
urlpatterns = [
url(r'^(?P<slug>[A-Z]+)/data/$', views.MainView.as_view(), name='main-view',),
...
]

Django - UpdateView with inline formsets trying to save duplicate records?

I have an Expense model and an ExpenseLineItem model. Just like a typical expense/invoice, one expense can have several line items to make up the total cost of an invoice. I'm trying to use class based views to create and update expenses. I've successfully coded the CreateView to make a new expense with multiple expense line items.
My problem is when I try and update an existing Expense which already has several expense line items. Here's my code below, and I can't figure out what the issue is. The mixins (TitleMixin, CancelSuccessMixin, SelectedApartment)are mine and work fine.
I'm getting an error that, I believe, means that it's trying to save a new copy of the ExpenseLineItems but fails since those already exist. Almost like I'm not providing an instance argument.
What am I doing wrong?
forms.py
class ExpenseForm(ModelForm):
class Meta:
model = Expense
fields = ['apart', 'inv_num', 'vendor', 'due_date']
ExpenseLineItemFormset = inlineformset_factory(Expense, ExpenseLineItem, fields=('description', 'account', 'amt'), can_delete=False)
Here's my ExpenseUpdate view:
class ExpenseUpdate(TitleMixin, CancelSuccessMixin, SelectedApartment, UpdateView):
model = Expense
form_class = ExpenseForm
template_name = 'accounting/expense.html'
def get(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
expense_line_item_form = ExpenseLineItemFormset(instance = self.object)
return self.render_to_response(self.get_context_data(form = form, expense_line_item_form = expense_line_item_form))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
expense_line_item_form = ExpenseLineItemFormset(self.request.POST, instance=self.object)
if (form.is_valid() and expense_line_item_form.is_valid()):
return self.form_valid(form, expense_line_item_form)
return self.form_invalid(form, expense_line_item_form)
def form_valid(self, form, expense_line_item_form):
self.object = form.save()
expense_line_item_form.instance = self.object
expense_line_item_form.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, expense_line_item_form):
return self.render_to_response(self.get_context_data(form=form, expense_line_item_form=expense_line_item_form))
Error code I get:
MultiValueDictKeyError at /stuff/2/accounting/update-expense/25/
"u'expenselineitem_set-0-id'"
Request Method: POST
Request URL: http://localhost:8000/stuff/2/accounting/update-expense/25/
Django Version: 1.8.3
Exception Type: MultiValueDictKeyError
Exception Value:
"u'expenselineitem_set-0-id'"
Exception Location: /usr/local/lib/python2.7/dist-packages/django/utils/datastructures.py in __getitem__, line 322
Edit: Relevant part of my template:
<form class="form-horizontal" action="" method="post">
{% csrf_token %}
{% load widget_tweaks %}
<div class="row">
<div class="col-md-12">
<table class="table table-tight">
<thead>
<th>Description</th>
<th class="text-right">Account</th>
<th class="text-right">Amount</th>
</thead>
<tbody>
{{ expense_line_item_form.management_form }}
{% for eli in expense_line_item_form %}
<tr>
<td>{{ eli.description|attr:'cols:29' }}</td>
<td class="text-right">{{ eli.account }}</td>
<td class="text-right">{{ eli.amt }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-md-12 text-right">
Cancel
<input class="btn btn-success btn-lg" type="submit" value="Post" />
</div>
<br><br>
</form>
EDIT -- Working Form Template I thought I would add the working version of my template, should someone else need it:
<tbody>
{{ expense_line_item_form.management_form }}
{% for eli in expense_line_item_form %}
<tr>
<td>{{ eli.id }} {{ eli.description|attr:'cols:29' }}</td> <!-- <<==== Here's where I simply added {{ eli.id }}. That's all I changed :) -->
<td class="text-right">{{ eli.account }}</td>
<td class="text-right">{{ eli.amt }}</td>
</tr>
{% endfor %}
</tbody>
You need to include the form id for each form in the formset (it won't be shown to the user, as it is rendered as a hidden input). Without that form, the value is missing from the POST data, and you get a KeyError as you are seeing.
From the formset docs:
Notice how we need to explicitly render {{ form.id }}. This ensures that the model formset, in the POST case, will work correctly. (This example assumes a primary key named id. If you’ve explicitly defined your own primary key that isn’t called id, make sure it gets rendered.)
In your case, you are looping through the formset with {% for eli in expense_line_item_form %}, so you need to include {{ eli.id }}.