Editing FormPreview form data before preview in Django - django

I'd like to alter the data collected from the form after a user clicks Preview but before it is shown to them again.
class StoryForm(forms.Form):
title = forms.CharField()
story = forms.CharField(widget=forms.Textarea)
class StoryFormPreview(FormPreview):
def done(self, request, cleaned_data):
# Do something with the cleaned_data, then redirect
# to a "success" page.
return HttpResponseRedirect('/form/success')
Before the preview is shown I want to append to whatever the user entered for the story field and add "Brought to you by so and so." How would I go about that? I've played around a lot with the process_preview and preview_post methods but couldn't get anything to work.

As I understand, you can modify it with
def process_preview(self, request, form, context):
"""
Given a validated form, performs any extra processing before displaying
the preview page, and saves any extra data in context.
"""
pass
UPD:
I found only solution with form clean method:
class StoryForm(forms.Form):
...
def clean_story(self):
self.cleaned_data['story'] = ...
return self.cleaned_data['story']
class StoryFormPreview(FormPreview):
def process_preview(self, request, form, context):
preview_data = {}
for key, value in form.cleaned_data.iteritems():
preview_data[key] = value
context['preview_data'] = preview_data
formtools/preview.html
{% for label, data in preview_data.iteritems %}
<tr>
<th>{{ label }}:</th>
<td>{{ data }}</td>
</tr>
{% endfor %}

Related

How to reuse Django's admin foreign key widget in admin intermediate pages

I haven't been able to find the answer anywhere on Django's documentation. Though, I'm not surprised given the question is a bit too complex to ask to a search engine.
I'm in a situation where I need to be able reassign a ForeignKey field for one or more entries of a model on Django's admin site.
So far, what I tried to do so using a custom action so that I can select the records I'm interested in and modify them all at once. But, then, I need to select the new related object I want their fk to be reassigned to. So, what I thought to do is an intermediate page where I'd display the fk widget I see all around the admin pages:
But it turns out this widget is really not designed to be publicly used. It's not documented and it's heavily complex to use. So far, I lost several hours digging into Django's code trying to figure how to use it.
I feel like I'm trying to do something really really exotic here so, if there's another solution, I'm all hears.
As shahbaz ahmad suggested, you can use ModelAdmin's autocomplete_fields which creates an select with autocompletion.
But if you're stuck with Django's foreign key widget, because, for instance, you have records which look the same and are indistinguishable in autocomplete, there is a solution.
It turns out ModelAdmin has a get_form method that you can use to retrieve the ModelForm used on the admin page. This method accepts a fields kwargs that you can use to select the fields you want to retrieve in the form. Use it like this:
class MyAdmin(ModelAdmin):
# define the admin subpath to your intermediate page
def get_urls(self):
return [
path(
"intermediate_page/",
self.admin_site.admin_view(self.intermediate_page),
name="intermediate_page",
),
*super().get_urls(),
]
def intermediate_page(self, request):
context = {
# The rest of the context from admin
**self.admin_site.each_context(request),
# Retrieve the admin form
"form": self.get_form(
request,
fields=[], # the fields you're interested in
)
}
return render(request, "admin/intermediate_page.html", context)
If your field is read only – which was my case – there's a workaround to an editable field: you can override the get_readonly_fields method which is called by get_form.
This method accepts an obj parameter which usually takes the model of the object being edited or None when creating a new entry. You can hijack this parameter to force get_readonly_fields exclude fields from read only fields:
def get_readonly_fields(self, request, obj=None):
readony_fields = super().get_readonly_fields(request, obj)
if not (
isinstance(obj, dict)
and isinstance(obj.get("exclude_from_readonly_fields"), Collection)
):
return readony_fields
return set(readony_fields) - set(obj["exclude_from_readonly_fields"])
get_form also has this obj parameter which it passes down to get_readonly_fields so you can call it like this:
# the fields you're interested in
include_fields = []
self.get_form(
request,
obj={"exclude_from_readonly_fields": include_fields},
fields=include_fields
)
Override changelist template of YourModelAdmin class to add one more button apart from add button.
#admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
change_list_template = "custom_your_model_change_list.html"
In custom_your_model_change_list.html,
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
<li>
<a class="button" href="{% url 'your_reassign_url_name' %}">Reassign</a>
</li>
{{ block.super }}
{% endblock %}
Mapped a view to 'your_reassign_url_name' to processed your request.
In urls.py,
urlpatterns = [
path('reassign/', YourReassignView, name='your_reassign_url_name'),
]
forms.py,
class ReassignForm(forms.Form):
# your reassign field
reassign_field = forms.ModelChoiceField(queryset='your queryset')
# Select multiple objects
updatable_objects = forms.ModelMultipleChoiceField(queryset='All objects queryset',
widget=forms.CheckboxSelectMultiple)
In views.py, On GET request you render a form with your required fields and on submit your update your data (reassign values) and after that you redirect admin change_list page.
def YourReassignView(request):
if request.method == 'POST':
form = ReassignForm(request.POST)
if form.is_valid():
# you get value after form submission
reassign_field = form.cleaned_data.get('reassign_field')
updatable_objects = form.cleaned_data.get('updatable_objects')
# your update query
YourModel.objects.filter(
id__in=updatable_objects.values('id')
).update(field_name=reassign_field)
#after that you redirect admin change_list page with a success message
messages.success(request, 'Successfully reassign')
return redirect(reverse('admin:app_label_model_name_changelist'))
else:
context_data = {'form': form}
return render(request, 'reassign_template.html', context=context_data)
else:
form = ReassignForm()
context_data = {'form': form}
return render(request, 'reassign_template.html', context=context_data)
In reassign_template.html,
<form method="POST" action="{% url 'your_reassign_url_name' %}">
{% csrf_token %}
{{ form.as_p }}
<br>
<input type="submit" value="submit">
</form>

How to bind more than one model to a template

I'm trying to feed a select tag with options recovered from a database.
The problem is, I'm totally beginner with Django and don't even know how to search for this.
I'm using generic view and as far as I know, the template is fed by a model bound to a context_object, default named as object_list, but you can change it in the context_object_name variable.
But my companies_object is not feeding the template.
<tbody>
{% for project in projects %}
<tr>
<td>
{{ project.title }}
</td>
[...]
<select>
{% for company in companies %}
<option value="{{company.id}}">{{company.name}}</option>
{% endfor %}
</select>
class ProjectsView(LoginRequiredMixin, ListView):
model = Project
context_object_name = 'projects'
template_name = 'projects/projects.html'
def select_company(self):
companies = Company.objects.all()
return 1 #return selected company
def get_projects(self):
seek_in_database()
return projects
I expect to know how to show two different objects in the same template, the projects, which is already working, and the companies object.
I didn't figure it out yet how the template is getting the project's data, I suspect of model = Projects and context_object_name.
I know that it is begginer level, and I don't expect someone to write a complete guide, I'll be very happy with some instruction of what subject to look.
Here an example how I do it:
class CompanyListView(ListView):
model = Company
context_object_name = 'companies'
template_name = 'core/company/listCompanies.html'
queryset = Company.objects.get_main_companies()
def get_context_data(self, **kwargs):
context = super(CompanyListView, self).get_context_data(**kwargs)
context.update({
'company_abbr': self.request.session.get('company_abbr'),
'page_title': 'Manage Companies',
})
return context
So in get_context_data, you may add as many data as you need.

How to display second edit form with current value in the form using django class based view

I have two forms as shown below
class TicketView(ObjectEditView):
form_class = forms.FirstForm
form_class2 = forms.SecondForm
model = First
def get(self, request, pk):
first = get_object_or_404(First, pk = pk)
return render(request, 'my_folder/file.html', {
'first': first,
'form': self.form_class,
"form2":self.form_class2
})
For the second form there is a drop down,with status as open, close and hold.
I am getting the drop down well using the below code
{% render_field form2.status %}
but it's not displaying current value as selected.
Is there anyway to assign current value of the drop down to the template and then set that value as selected in the drop down using the render_field?
Something like
{% render_field form2.status value='close' %}

Save post data from a form with listview

i have a listview of form:
class MatchsView(ListView):
model = Match2x1
template_name = 'matchs.html'
and the template render this:
{% for match in object_list %}
<form action="/apostar/" method="post">{% csrf_token %}
<p><input type="radio" name="{{match.id}}" value="{{match.team_a}}">{{match.team_a}}</input></p>
<input type="submit" value="Apostar"></input>
</form>
{% endfor %}
as you can see each form has two fields, i need to save in DB the values that the user choose, with FormView its easy, but since this time is a ListView im a little bit lose to save in DB from a form, i know that i have to create a view that handles the form, but really i dont know how to create the view that handles the post data of each form. For example lets says that i need to save the post data in a model called FormsMatchs, how can i do it?
i was trying with this:
class FormView(FormView):
form_class = FormMatch
success_url = '/'
template_name = 'matchs.html'
def post(self, request, *args, **kwargs):
hola = Country.objects.create(name=request.POST)
but is saving this:
<QueryDict: {u'csrfmiddlewaretoken': [u'tCIQuGlSXKJL0R5eo9R5w09ldeBt7zNW'], u'5': [u'River']}>
Your best bet if you want to have a simple list of a given model but also accept a Form is to use a FormView, and override get_context_data(self, **kwargs) to pass a queryset into the context, like so:
def get_context_data(self, **kwargs):
context = super(MatchsView, self).get_context_data()
context['object_list'] = Match2x1.objects.all()
return context
However, you can also use a FormMixin with a ListView, see an example here.

CharField values disappearing after save (readonly field)

I'm implementing simple "grade book" application where the teacher would be able to update the grades w/o being allowed to change the students' names (at least not on the update grade page). To do this I'm using one of the read-only tricks, the simplest one. The problem is that after the SUBMIT the view is re-displayed with 'blank' values for the students. I'd like the students' names to re-appear.
Below is the simplest example that exhibits this problem. (This is poor DB design, I know, I've extracted just the relevant parts of the code to showcase the problem. In the real example, student is in its own table but the problem still exists there.)
models.py
class Grade1(models.Model):
student = models.CharField(max_length=50, unique=True)
finalGrade = models.CharField(max_length=3)
class Grade1OForm(ModelForm):
student = forms.CharField(max_length=50, required=False)
def __init__(self, *args, **kwargs):
super(Grade1OForm,self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['student'].widget.attrs['readonly'] = True
self.fields['student'].widget.attrs['disabled'] = 'disabled'
def clean_student(self):
instance = getattr(self,'instance',None)
if instance:
return instance.student
else:
return self.cleaned_data.get('student',None)
class Meta:
model=Grade1
views.py
from django.forms.models import modelformset_factory
def modifyAllGrades1(request):
gradeFormSetFactory = modelformset_factory(Grade1, form=Grade1OForm, extra=0)
studentQueryset = Grade1.objects.all()
if request.method=='POST':
myGradeFormSet = gradeFormSetFactory(request.POST, queryset=studentQueryset)
if myGradeFormSet.is_valid():
myGradeFormSet.save()
info = "successfully modified"
else:
myGradeFormSet = gradeFormSetFactory(queryset=studentQueryset)
return render_to_response('grades/modifyAllGrades.html',locals())
template
<p>{{ info }}</p>
<form method="POST" action="">
<table>
{{ myGradeFormSet.management_form }}
{% for myform in myGradeFormSet.forms %}
{# myform.as_table #}
<tr>
{% for field in myform %}
<td> {{ field }} {{ field.errors }} </td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Submit">
</form>
Your way of displaying the readonly field is the problem.
Since the student field is disabled, the form submit will not have that as the input, so the error form that is displayed with validation error messages don't get the initial value.
That is why ReadOnly Widget has to be more complex than just being a html disabled field.
Try using a real ReadOnlyWidget, one that overrides _has_changed.
Following is what I use. For instantiation, it takes the original_value and optionally display_value, if it is different.
class ReadOnlyWidget(forms.Widget):
def __init__(self, original_value, display_value=None):
self.original_value = original_value
if display_value:
self.display_value = display_value
super(ReadOnlyWidget, self).__init__()
def _has_changed(self, initial, data):
return False
def render(self, name, value, attrs=None):
if self.display_value is not None:
return unicode(self.display_value)
return unicode(self.original_value)
def value_from_datadict(self, data, files, name):
return self.original_value
I'm stretching myself a little here, so some thoughts:
% Have you sniffed the traffic to see exactly what's being sent between browser and server?
% Do you need to send the student name as a hidden field (your db update thing may assume you want student blank if you don't)?
% Have you looked at the source of your HTML after Python parses it?