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()
Related
Basically, I would like to get some advice about how to transition from a form page back to a previous page when the form is submitted.
There are other posts on similar situations but none seem to show how to implement this using a class-based view, so any advice here would be most appreciated.
So far I have come up with the approach below. Here, I am trying to pass a parameter holding the URL of the previous page, from the template for the previous page to urls.py and then onto the view. However, I don't think I am using the correct syntax in urls.py as the value of the previous_url parameter in the view is simply "previous_url".
Link in the template for the table page
Edit
URL
path('entry/<int:pk>/edit/', EntryUpdateView.as_view(previous_url="previous_url"), name='entry_update'),
View
class EntryUpdateView(LoginRequiredMixin, UpdateView):
model = Entry
template_name = 'entry_update.html'
fields = ('source', 'target', 'glossary', 'notes')
previous_url = ""
def form_valid(self, form):
obj = form.save(commit=False)
obj.updated_by = self.request.user
obj.save()
return HttpResponseRedirect(self.previous_url)
Update form
<form method="POST" novalidate>
{% csrf_token %}
<div class="mb-3">
{{ form.source|as_crispy_field }}
</div>
<div class="mb-3">
{{ form.target|as_crispy_field }}
</div>
<div class="mb-3">
{{ form.glossary|as_crispy_field }}
</div>
<div class="mb-3">
{{ form.notes|as_crispy_field }}
</div>
<div class="text-center mt-3">
<button type="submit" class="btn btn-outline-primary mx-2">Update</button>
</div>
</form>
Error
Page not found (404)
Request Method: GET
Request URL: http://localhost:8000/entry/5131/edit/previous_url
If this helps anyone, I was able to get the value of previous_url within form_valid() using self.request.GET.get('previous_url').
def form_valid(self, form):
obj = form.save(commit=False)
obj.updated_by = self.request.user
obj.save()
previous_url = self.request.GET.get('previous_url')
return HttpResponseRedirect(previous_url)
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
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',),
...
]
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',),
...
]
I have a simple Django form being passed through a view to a template where it should display, but, for a reason that I -- after 5 hours -- have failed to deduce, it does not. Any and all ideas welcome, I'm dying to solve this irksome problem.
I have the following Django form:
class BandAddToCartForm(forms.Form):
LENGTH_CHOICES = ( ('XS', 'XS'),
('S', 'S'),
('M', 'M') )
length = forms.Select(choices=LENGTH_CHOICES)
quantity = forms.IntegerField(widget=forms.HiddenInput())
band_sku = forms.CharField(widget=forms.HiddenInput())
# override the default __init__ so we can set the request
def __init__(self, request=None, *args, **kwargs):
self.request = request
super(BandAddToCartForm, self).__init__(*args, **kwargs)
# custom validation to check for cookies
def clean(self):
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError("Cookies must be enabled.")
return self.cleaned_data
It is passed to the template through the following view:
def show_custom_watches(request,
template_name="catalog/custom_watches.html"):
bands = Band.objects.all()
page_title = "Custom Watches"
meta_keywords = "custom, watches, beaded"
meta_description = "Custom beaded watches for every occassion."
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
# need to evaluate the HTTP method
if request.method == 'POST':
#add to cart, create bound form
postdata = request.POST.copy()
form = BandAddToCartForm(request, postdata)
#check if posted data is valid
if form.is_valid():
#add to cart and redirect to cart page
cart.add_band_to_cart(request)
# if test cookie worked, get rid of it
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
url = urlresolvers.reverse('show_cart')
return HttpResponseRedirect(url)
else:
# it's a GET, create the unbound form. Note request as a kwarg
band_form = BandAddToCartForm(request=request, label_suffix=':')
# set the test cookie on our first GET request
request.session.set_test_cookie()
return render_to_response("catalog/custom_watches.html",
locals(),
context_instance=RequestContext(request))
Lastly, here is the relevant bit of template where the form is failing to display:
{% for b in bands %}
<div class="watch_list_item">
<img class="" src="{{ MEDIA_URL }}images/bands/thumbnails/{{ b.image }}" alt="{{ b.name }}" />
<div class="watch_form_area">
<p>{{ b.name }}</p>
<form method="post" action="." class="cart">{% csrf_token %}
{{ band_form.as_p }}
<input type="submit" value="Add To Cart" name="add_product" alt="Add To Cart" class="add_to_cart_button" id="add_only_product" />
</form>
</div>
</div>
{% endfor %}
The Add to cart button appears as it should, but the length selector completely fails to display. Any ideas?
The first
return render_to_response(template_name,
locals(),
context_instance=RequestContext(request))
always happens before you initialise the form, remove it and it should work.