I have problem with a form if invalid data is submitted. My ErrorView view uses the app/feriehus_detail.html template, but I'm not including price_data in the template context.
This seems to cause a KeyError when the template tries to use price_data as an argument in a filter.
I can't figure out how to add it to the template context? Any help will be much appreciated.
I'm using Python 3.5 and Django 1.9.
Traceback:
Template error:
In template C:\Conference\app\templates\app\feriehus_detail.html, error at line 221
Failed lookup for key [%s] in %r 211 : <td class="text-center">{{ object.price_rec_F }} kr</td>
213 : <td class="text-center">{{ object.price_rec_F|percentage_dif:object.price_cur_F }}</td>
214 : </tr>
215 : </tbody>
216 : </table>
217 : </div>
218 : <div class="col-xs-12" style="padding-bottom:25px;">
219 : <div class="grid">
220 : <div class="col-1-1">
221 : {% column_chart price_data with height='500px' %}
222 : </div>
223 : </div>
224 : </div>
template:
<form role="form" action="{% url 'error' pk=feriehus.id %}" method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit">Send</button>
</form>
forms.py:
class ErrorForm(forms.Form):
content = forms.CharField(
required=True,
widget=forms.Textarea
)
def __init__(self, *args, **kwargs):
super(ErrorForm, self).__init__(*args, **kwargs)
self.fields['content'].label = "Beskriv fejl"
self.helper = FormHelper()
urls.py:
url(r'^feriehus/(?P<pk>[0-9]+)/$', views.FeriehusDetail.as_view(), name='feriehus_detail'),
url(r'^feriehus/(?P<pk>[0-9]+)/error/$', views.ErrorView.as_view(), name='error'),
views.py:
class FeriehusDetail(DetailView, FormMixin):
model = Feriehus
form_class = ErrorForm
def get_context_data(self, **kwargs):
context = super(FeriehusDetail, self).get_context_data(**kwargs)
context['price_data'] = CreateContext.price_time_serie(pk=self.kwargs['pk'])
return context
class ErrorView(FormView):
form_class = ErrorForm
template_name = 'app/feriehus_detail.html'
def get_success_url(self, **kwargs):
return reverse_lazy('feriehus_detail', kwargs={'pk': self.kwargs['pk']})
def get_context_data(self, **kwargs):
context = super(ErrorView, self).get_context_data(**kwargs)
context['object'] = get_object_or_404(Feriehus, pk=self.kwargs['pk'])
context['feriehus'] = get_object_or_404(Feriehus, pk=self.kwargs['pk'])
#context['price_data'] = get_object_or_404(CreateContext.price_time_serie(pk=self.kwargs['pk']))
return context
def form_valid(self, form, **kwargs):
form_content = form.cleaned_data['content']
template = get_template('error_template.txt')
context = Context({
'form_content': form_content
})
content = template.render(context)
email = EmailMessage(
'mail',
content,
'from#email.com' + '',
['to#email.com']
)
email.send()
return super(FeedbackView, self).form_valid(form, **kwargs)
Output of CreateContext.price_time_serie(pk=self.kwargs['pk']):
[{'data': [('Week 49', 654645), ('Week 01', 554645)], 'name': 'Recommended price'}, {'data': [('Week 49', 3398), ('Week 01', 3398)], 'name': 'Current price'}]
As the comment above says, I dont know what CreateContext.price_time_serie() is supposed to do. Until you'll explain that we can only speculate what you are trying to achieve there.
If that is a way how to obtain the primary key for CreateContext record, then you have to add another parameter to the function, as get_object_or_404() expected at least two parameters - the first one is the model class you are trying to get, the others are parameters for the SQL query to identify the record to get. So I guess it should be something like this:
def get_context_data(self, *args, **kwargs):
...
context['price_data'] = get_object_or_404(CreateContext, pk=CreateContext.price_time_serie(pk=self.kwargs['pk']))
...
Related
I'm trying to make an UpdateView to edit user profile without pk or slug using get_object() with self.request.user. But when I run my settings, the form is not shown. Here is my code:
urls.py:
urlpatterns = [
path('settings/', UserSettings.as_view(), name='settings'),
path('<slug:profile_slug>/', ShowUserProfile.as_view(), name='profile'),
]
views.py:
class UserSettings(LoginRequiredMixin, DataMixin, UpdateView):
template_name = 'users/user_settings.html'
form_class = UpdateUserForm
def get_object(self, *args, **kwargs):
return self.request.user
def get_success_url(self):
return reverse_lazy('profile', kwargs={'profile_slug': self.request.user.userpofile.slug})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='Settings')
forms.py:
class UpdateUserForm(forms.ModelForm):
class Meta:
model = User
fields = ('username', 'password')
widgets = {'username': forms.TextInput(attrs={'class': 'form-control form-input form__input'}),
'password': forms.TextInput(attrs={'class': 'form-control form-input form__input'})}
user_settings.html:
<form class="form" method="post">
{% csrf_token %}
{% for item in form %}
<div class="row">
<div class="col-sm-4">
<label class="form__label" for="{{item.id_for_label}}">{{item.label}}: </label>
</div>
<div class="col-sm-8">
{{item}}
</div>
</div>
<div class="form__errors">{{item.errors}}</div>
{% endfor %}
<button class="button" type="submit">Set settings</button>
</form>
It doesn't even throw an error. What I need to do?
You need to return you get_context_data function, like so:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
c_def = self.get_user_context(title='Settings')
return context
The view has a Boolean Field which will define if a question is OK or needs correction.
The template will load two buttons to act as submit to the form, "Question is OK" and "Question needs correction".
I need to pass the value of this button as the Boolean Field value.
I found the answer when using Function-based views, but I'm using Class-based views, so I don't know how to pass the request.POST values.
Here's my views.py and forms.py:
views.py
class QuestionValidation(PermissionRequiredMixin, UpdateView):
permission_required = 'users.validator'
model = Question
form_class = ValidationForm
template_name = 'question_validation.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
context['question'] = Question.objects.filter(
question_order=self.kwargs['order']).get(id_by_order=self.kwargs['id_by_order'])
context['order'] = self.kwargs['order']
context['id_by_order'] = self.kwargs['id_by_order']
return context
def get_object(self, *args, **kwargs):
question_order = Q(question_order__id=self.kwargs['order'])
question_id = Q(id_by_order__contains=self.kwargs['id_by_order'])
q = Question.objects.get(question_order & question_id)
return get_object_or_404(Question, pk=q.id)
def get_success_url(self, *args, **kwargs):
view_name = "order-detail"
return reverse(view_name, kwargs={'pk': self.kwargs['order']})
forms.py
class ValidationForm(forms.ModelForm):
class Meta:
model = Question
fields = ['revision_report', 'revision_approval']
widgets = {
'revision_report': forms.HiddenInput(),
'revision_approval': forms.HiddenInput(),
}
and part of the template that this code will be loaded:
<form action="" method="POST">{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-success" name="question_approved">Questão aprovada</button>
<button class="btn btn-danger" name="question_refused">Questão não foi aprovada</button>
</form>
<br><br>
<script src="{% static 'js/hoverValidatorTextbox.js' %}"></script>
{% endblock %}
As Ene P told in the comments the following link solves it.
https://docs.djangoproject.com/en/3.2/topics/class-based-views/intro/#handling-forms-with-class-based-views
I have a form in ListView view using FormMixin that form takes a string lastname
and querys to take a queryset of Account object, I want that Queryset on my template via extra_context.
The form works and I have the queryset that I want, I pass it into self.extra_context and I can see it on console with print BUT it doesn't show in my template. Im feeling stacked, please help me if tou can.
class DepartmentFilesListView(LoginRequiredMixin, SelectRelatedMixin,FormMixin, ListView):
model = FileDetail
login_url = '/'
template_name = 'incomingfiles/departmentFileList_All.html'
context_object_name = 'departmentFiles'
form_class = ChooseOperatorForm
success_url = reverse_lazy('incomingfiles:DepartmentFilesListView')
def __init__(self):
super(DepartmentFilesListView, self).__init__()
now = timezone.now()
self.extra_context = {'title':'Έγγραφα Τμήματος','date':now.date(), 'day':getDay(now.weekday())}
def dispatch(self, request, *args, **kwargs):
return super(DepartmentFilesListView, self).dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
# for
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
lastname = form.cleaned_data['lastname']
lastname = valuateLastname(lastname)
self.extra_context['operators'] = Account.objects.filter(
is_active=True,
department=self.request.user.department,
last_name__contains=lastname
)
print(self.extra_context['operators'])
return super(DepartmentFilesListView, self).form_valid(form)
def form_invalid(self, form):
return super(DepartmentFilesListView, self).form_invalid(form)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(**kwargs)
context['filter_for_all'] = FileDetailFilter(self.request.GET, queryset=self.get_queryset())
num_in_page = 50
paginated_filter_for_all = Paginator(context['filter_for_all'].qs, num_in_page)
filter_for_all_page_number = self.request.GET.get('page')
filter_for_all_page_obj = paginated_filter_for_all.get_page(filter_for_all_page_number)
context['filter_context_obj'] = filter_for_all_page_obj
return context
def get_queryset(self):
self.department = self.request.user.department
return FileDetail.objects.filter(Q(Q(apantitiko__operator__department=self.department)
| Q(departmentassignment__department=self.department)))
And this is the sample of my template
<div class="col-12 Search-operator-div">
<div class="row">
<h2>Αναζήτηση χρεωμένων εγγράφων κατα χειριστή</h2>
</div>
<div class="row" style="width:100%; border-top: 2px solid #212529;"></div>
<br>
<form id="exactΟperator" method="post">
{% csrf_token %}
<div class="row">
{% for field in form %}
<div class="col-1 align-self-center">
<label id="id_{{ field.name }}_label">{{ field.label }}</label>
</div>
<div class="col-3 align-self-center">
{{ field }}
{{ field.error }}
<a role="button" id="cancelOperator" type="button">
<i class="fas fa-times"></i></a>
</div>
{% endfor %}
<div class="col-1 align-self-center">
<input id="SearchOperator" type="submit" value="Αναζήτηση" class="btn btn-primary btn-sm">
</div>
</div>
</form>
<div class="row search-operator-div-content">
{% for operator in operators %}
<div class="col">
<a href="{% url 'incomingfiles:OperatorFilesListView' pk=operator.pk %}" class="link-transition btn-link">
{{ operator }}</a>
</div>
{% endfor %}
</div>
<br>
</div>
In my template I can see anything else is in my self.extra_context but anything Im tried to pass in my form_valid function doesn't shown.
It is like my extra_context cant update in this function.
The default implementation for form_valid() simply redirects to the success_url.
So it returns:
return HttpResponseRedirect(self.get_success_url())
And calling extra_context_data is skipped.
SOLUTION
Return HttpResponse instead super(DepartmentFilesListView, self).form_valid(form):
def form_valid(self, form):
lastname = form.cleaned_data['lastname']
lastname = valuateLastname(lastname)
self.extra_context['operators'] = Account.objects.filter(
is_active=True,
department=self.request.user.department,
last_name__contains=lastname
)
print(self.extra_context['operators'])
return render(self.request, self.template_name, self.get_context_data())
I think this is the easiest method:
class SomeListView(ListView):
.....
extra_context = self.myquery()
.....
def myquery(self):
return { 'query': QuerySet}
Then in template
{{query}}
Well I found a solution but I don't know if that's the correct way to solve that problem.
If you have anything to suggest it will be my pleasure to try it.
To solve my problem I pass the lastname value into the session and query in get_context_data .
def form_valid(self, form):
lastname = form.cleaned_data['lastname']
lastname = valuateLastname(lastname)
if lastname == '':
lastname = '-'
elif lastname == '*':
lastname = ''
self.request.session['lastname'] = lastname
# print(self.extra_context['operators'])
return super(DepartmentFilesListView, self).form_valid(form)
def form_invalid(self, form):
print(self.extra_context['operators'])
return super(DepartmentFilesListView, self).form_invalid(form)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(**kwargs)
context['filter_for_all'] = FileDetailFilter(self.request.GET, queryset=self.get_queryset())
num_in_page = 50
paginated_filter_for_all = Paginator(context['filter_for_all'].qs, num_in_page)
filter_for_all_page_number = self.request.GET.get('page')
filter_for_all_page_obj = paginated_filter_for_all.get_page(filter_for_all_page_number)
context['operators'] = Account.objects.filter(is_active=True,
department=self.request.user.department,
last_name__contains=self.request.session['lastname'])
context['filter_context_obj'] = filter_for_all_page_obj
return context
I have a model
class MyModel(models.Model):
slug = models.UUIDField(default=uuid4, blank=True, editable=False)
advertiser = models.ForeignKey(Advertiser)
position = models.SmallIntegerField(choices=POSITION_CHOICES)
share_type = models.CharField(max_length=80)
country = CountryField(countries=MyCountries, default='DE')
# some other Fields. Edited in a ModelForm
This view is called by a url containg position, share_type, country as parameters. I would like to display these parameters in the template. What is the best way to do this. I already have these possibilies
1) use get_context_date and store this in the context
def get_context_data(self, **kwargs):
ctx = super(MyModel, self).get_context_data(**kwargs)
ctx['share_type'] = self.kwargs.get('share_type', None)
ctx['country'] = self.kwargs.get('country', None)
ctx['postal_code'] = self.kwargs.get('postal_code', None)
ctx['position'] = int(self.kwargs.get('position', None))
return ctx
This can then be used in the template
2) use the view variant
def share_type(self):
ret = self.kwargs.get('share_type', None)
return ret
def country(self):
ret = self.kwargs.get('country', None)
return ret
like
<div class="row">
<strong>
<div class="col-sm-3">
Type : {{ view.share_type }}
<div class="col-sm-3">
Country : {{ view.country }}
I think both way are somewhat redundant. Does anybody know a more generic approach to this.
Kind regards
Michael
I think the best way is to do it like this :
def dispatch(self, request, *args, **kwargs):
self.share_type = self.kwargs.get('share_type', None)
self.country = self.kwargs.get('country', None)
self.postal_code = self.kwargs.get('postal_code', None)
self.position = int(self.kwargs.get('position', None))
self.position_verbose = verbose_position(self.position)
ret = super(CreateAdvertisment, self).dispatch(request, *args, **kwargs)
return ret
You can use then in the form_valid method
def form_valid(self, form):
form.instance.advertiser = self.advertiser
form.instance.share_type = self.share_type
form.instance.country = self.country
form.instance.postal_code = self.postal_code
form.instance.position = self.position
ret = super(CreateAdvertisment, self).form_valid(form)
return ret
and in the template of course like this
<strong>
<div class="col-sm-3">
Typ : {{ view.share_type }}
</div>
<div class="col-sm-3">
PLZ : {{ view.postal_code }}
</div>
<div class="col-sm-3">
Ort : TODO
</div>
<div class="col-sm-3">
Pos : {{ view.position_verbose }}
</div>
Many thanks for the proposals.
Regards
Michael
I am using Django 1.10 and django-crispy-forms 1.6.1 in my project. I added a custom datetimepicker dropdown template that seems to be functioning correctly. The problem I am now encountering, is that every time I attempt to submit a form, I am flashed with the error on the field that it is required. It seems that after submitting the form, the field is cleared out, and the error class is added to the field.
After submit, form shows errors
When I checked the console, I receive no errors, but when I check Chrome DevTools, I looked at the request payload sent server-side and I see that the field is not added at all to the request. Here is a picture of the request payload from the dev tools.
DevTools request payload without "time_stamp"
I am able to get the datetimepicker to dropdown when the input receives focus, and that seems to work just fine. Here is the relevant code for my forms/views, and the custom template.html:
models.py
class Call(models.Model):
history = AuditlogHistoryField()
analyst = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, blank=True, null=True, limit_choices_to={'is_staff': True}, related_name='call_analyst')
contact = models.CharField(max_length=50)
time_stamp = models.DateTimeField(default=datetime.now)
description = models.CharField(max_length=512)
ticket = models.PositiveIntegerField(blank=True, null=True)
CALL_TYPE = (
('M', 'Made'),
('R', 'Received'),
)
call_type = models.CharField(max_length=1, choices=CALL_TYPE)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
#python_2_unicode_compatible
def __str__(self):
return "Call instance {}".format(self.pk)
forms.py
class CallForm(forms.ModelForm):
description = forms.CharField(widget=forms.Textarea)
class Meta:
model = Call
fields = ['analyst', 'contact', 'time_stamp', 'description', 'ticket', 'call_type']
def __init__(self, *args, **kwargs):
super(CallForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_tag = True
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-sm-3'
self.helper.field_class = 'col-sm-9'
self.helper.layout = Layout(
Field('analyst'),
Field('contact'),
Field('time_stamp', template="datetimepicker.html"),
Field('description'),
Field('ticket'),
Field('call_type'),
)
def is_valid(self):
return super(CallForm, self).is_valid()
def full_clean(self):
return super(CallForm, self).full_clean()
def clean_analyst(self):
analyst = self.cleaned_data.get("analyst", None)
return analyst
def clean_contact(self):
contact = self.cleaned_data.get("contact", None)
return contact
def clean_time_stamp(self):
time_stamp = self.cleaned_data.get("time_stamp", None)
return time_stamp
def clean_description(self):
description = self.cleaned_data.get("description", None)
return description
def clean_ticket(self):
ticket = self.cleaned_data.get("ticket", None)
return ticket
def clean_call_type(self):
call_type = self.cleaned_data.get("call_type", None)
return call_type
def clean(self):
return super(CallForm, self).clean()
def validate_unique(self):
return super(CallForm, self).validate_unique()
def save(self, commit=True):
return super(CallForm, self).save(commit)
views.py
class CallCreateView(AjaxCreateView):
model = Call
form_class = CallForm
# fields = ['analyst', 'contact', 'call_timestamp', 'description', 'ticket', 'call_type']
template_name = "reports/../_templates/create_update_template.html"
success_url = reverse_lazy("call_list")
def __init__(self, **kwargs):
super(CallCreateView, self).__init__(**kwargs)
def dispatch(self, request, *args, **kwargs):
return super(CallCreateView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
return super(CallCreateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return super(CallCreateView, self).post(request, *args, **kwargs)
def get_form_class(self):
return super(CallCreateView, self).get_form_class()
def get_form(self, form_class=None):
return super(CallCreateView, self).get_form(form_class)
def get_form_kwargs(self, **kwargs):
return super(CallCreateView, self).get_form_kwargs()
def get_initial(self):
return super(CallCreateView, self).get_initial()
def form_invalid(self, form):
return super(CallCreateView, self).form_invalid(form)
def form_valid(self, form):
obj = form.save(commit=False)
obj.save()
return super(CallCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
ret = super(CallCreateView, self).get_context_data(**kwargs)
return ret
def render_to_response(self, context, **response_kwargs):
return super(CallCreateView, self).render_to_response(context, **response_kwargs)
def get_template_names(self):
return super(CallCreateView, self).get_template_names()
def get_success_url(self):
return reverse("call_detail", args=(self.object.pk,))
datetimepicker.html
{% load crispy_forms_field %}
<div{% if div.css_id %} id="{{ div.css_id }}"{% endif %}
class="form-group{% if form_show_errors and field.errors %} has-error{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}{% if div.css_class %} {{ div.css_class }}{% endif %}">
{% if field.label and form_show_labels %}
<label for="{{ field.id_for_label }}"
class="control-label {{ label_class }}{% if field.field.required %} requiredField{% endif %}">
{{ field.label|safe }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
</label>
{% endif %}
<div class="controls date datetimepicker col-sm-9" id="{{ field.id_for_label }}"
data-link-field="{{ field.id_for_label }}" {{ flat_attrs|safe }}>
<input class="form-control" id="{{ field.id_for_label }}" type="text">
</div>
<input type="hidden" id="{{ field.id_for_label }}" value=""/><br/>
{% include 'bootstrap3/layout/help_text_and_errors.html' %}
</div>
{% block datetimepicker %}
<script type="text/javascript">
$(function () {
$('#{{ field.id_for_label }}').datetimepicker({
sideBySide: true,
allowInputToggle: true,
showTodayButton: true,
showClear: true,
showClose: true,
toolbarPlacement: "top",
format: "dddd, MMMM Do YYYY, h:mm A"
})
})
</script>
{% endblock %}
I think there must be a problem either with my template, or how django-crispy-forms handles fields that use a custom template, because when I remove the template from the form helper, a regular input box is used and the datetime string is submitted just fine to django. Thank you all for your help. Sorry that I had to link the images, I don't have a high enough rep to embed them.
I think the reason why the time_stamp is not being sent to the server because the datepicker input tag doesn't have a name attribute:
<input class="form-control" id="{{ field.id_for_label }}" type="text">
Have a look at the notes in w3schools:
Note: Only form elements with a name attribute will have their values passed when submitting a form.