Retrieving models from form with ModelMultipleChoiceField - django

I am having difficulties with forms, specifically ModelMultipleChoiceField.
I've pieced together this code from various examples, but it sadly doesn't work.
I would like to be able to:
Search for some Works on work_search.html
Display the results of the search, with checkboxes next to each result
Select the Works I want, via the checkboxes
After pressing Add, display which works were selected.
I believe everything is okay except the last part. The page simply displays "works" :(
Here is the code - sorry about the length.
Models.py
class Work(models.Model):
title = models.CharField(max_length=200)
artist = models.CharField(max_length=200)
writers = models.CharField(max_length=200)
def __unicode__(self):
return self.title + ' - ' + self.artist
forms.py
class WorkSelectForm(forms.Form):
def __init__(self, queryset, *args, **kwargs):
super(WorkSelectForm, self).__init__(*args, **kwargs)
self.fields['works'] = forms.ModelMultipleChoiceField(queryset=queryset, widget=forms.CheckboxSelectMultiple())
views.py
def work_search(request):
query = request.GET.get('q', '')
if query:
qset = (
Q(title__icontains=query) |
Q(artist__icontains=query) |
Q(writers__icontains=query)
)
results = Work.objects.filter(qset).distinct()
form = WorkSelectForm(results)
return render_to_response("work_search.html", {"form": form, "query": query })
else:
results = []
return render_to_response("work_search.html", {"query": query })
def add_works(request):
#if request.method == POST:
form = WorkSelectForm(request.POST)
#if form.isvalid():
items = form.fields['works'].queryset
return render_to_response("add_works.html", {"items":items})
work_search.html
{% extends "base.html" %}
{% block content %}
<h1>Search</h1>
<form action="." method="GET">
<label for="q">Search: </label>
<input type="text" name="q" value="{{ query|escape }}">
<input type="submit" value="Search">
</form>
{% if query %}
<h2>Results for "{{ query|escape }}":</h2>
<form action="add_works" method="post">
<ul>
{% if form %}
{{ form.as_ul }}
{% endif %}
</ul>
<input type="submit" value="Add">
</form>
{% endif %}
{% endblock %}
add_works.html
{% extends "base.html" %}
{% block content %}
{% if items %}
{% for item in items %}
{{ item }}
{% endfor %}
{% else %}
<p>Nothing selected</p>
{% endif %}
{% endblock %}

In add_works, you're not constructing your WorkSelectForm the right way. It's expecting as a first parameter the queryset of possible/authorized choices, then the POST data.
Also, you're not accessing the selected works correctly from the form. You have to use is_valid method on the form, then use cleaned_data as described in the doc.
From what I see in your work_search view, there's no restriction on which Work objects you can search then add to the result, so you could do simply:
def add_works(request):
#if request.method == POST:
form = WorkSelectForm(Work.objects.all(), request.POST)
if form.is_valid():
# the items are in form.cleaned_data['works']
items = form.cleaned_data['works']
return render_to_response("add_works.html", {"items":items})
else:
# handle error case here
...

Related

how to pass context (placeholder in my case) to django admin Simple List filter

I am creating a college management system with facility to store quiz/tests in the website and get student performance. I wanted to filter quiz based on month year, such as show all quiz of march 2022.
For this purpose, I can not show all months and years in the sidebar, its useless for the first, second it will take lot of space and client has to scroll a lot.
So I created a custom Simple Text Input by following some blogs. Here is that generic filter code:
class SimpleTextInputFilter(admin.SimpleListFilter):
template = 'admin/input_filter.html'
def filter(self, request, queryset, value):
raise NotImplementedError('Implement this method in subclasses.')
def queryset(self, request, queryset):
if self.value() is not None:
return self.filter(request, queryset, self.value())
def lookups(self, *args, **kwargs):
return None
def has_output(self):
return True
def choices(self, changelist):
all_choice = next(super().choices(changelist))
all_choice['query_parts'] = (
(k, v)
for k, v in changelist.get_filters_params().items()
if k != self.parameter_name
)
yield all_choice
And here is how I am using it:
class QuizMonthYearFilter(SimpleTextInputFilter):
parameter_name = 'date'
title = 'Quiz (Month Year)'
def filter(self, request, queryset, value):
try:
date = datetime.strptime(value, '%b %Y')
except:
pass
else:
return queryset.filter(date__year=date.year, date__month=date.month)
and admin/input_filter.html is:
{% load i18n %}
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
<ul>
<li>
{% with choices.0 as all_choice %}
<form method="GET" action="">
{% for k, v in all_choice.query_parts %}
<input type="hidden" name="{{ k }}" value="{{ v }}" />
{% endfor %}
<input type="text"
value="{{ spec.value|default_if_none:'' }}"
name="{{ spec.parameter_name }}"
placeholder={{ **GET VALUE HERE** }}/>
{% if not all_choice.selected %}
<strong>x {% trans 'Remove' %}</strong>
{% endif %}
</form>
{% endwith %}
</li>
</ul>
Now I want to pass placeholder to the input of the filter. So I want to pass context data to the html template, please suggest me how I can do that, so that I can give user hint about the date format.

Show django form in a designed page

How are you?
I m totally new in Django.I designed a page and I wanted to show a django form(edit or create) in a well designed HTML page. but i do not know how.
This is my owner method:
class OwnerUpdateView(LoginRequiredMixin, UpdateView):
"""
queryset to the requesting user.
"""
def get_queryset(self):
print('update get_queryset called')
""" Limit a User to only modifying their own data. """
qs = super(OwnerUpdateView, self).get_queryset()
return qs.filter(user=self.request.user)
class OwnerCreateView(LoginRequiredMixin, CreateView):
"""
Sub-class of the CreateView to automatically pass the Request to the Form
and add the owner to the saved object.
"""
# Saves the form instance, sets the current object for the view, and redirects to get_success_url().
def form_valid(self, form):
print('form_valid called')
object = form.save(commit=False)
object.user = self.request.user
object.save()
return super(OwnerCreateView, self).form_valid(form)
This is my views.py
class TaskUpdateView(OwnerUpdateView):
model = Task
fields = ["title", "text", "endDate"]
class TaskCreateView(OwnerCreateView):
model = Task
fields = ["title","text","status","endDate"]
This is my urls.py:
app_name='task'
urlpatterns = [
path('', views.TaskListView.as_view(), name='all'),
path('task/<int:pk>/', views.TaskDetailView.as_view(), name='detail'),
path('task/create', views.TaskCreateView.as_view(success_url=reverse_lazy('task:all')), name='task_create'),
path('task/update/<int:pk>', views.TaskUpdateView.as_view(success_url=reverse_lazy('task:all')),
name='task_update'),
path('task/delete/<int:pk>', views.TaskDeleteView.as_view(success_url=reverse_lazy('task:all')),
name='task_delete'),
path("accounts/login/", views.login, name='login'),
path("accounts/logout/", views.logout, name='logout'),
]
And this is the models.py:
class Task(models.Model):
title=models.CharField(max_length=250)
text=models.TextField()
user=models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=False)
status=models.ForeignKey('Status',on_delete=models.SET_NULL,null=True)
startDate=models.DateTimeField(auto_now_add=True)
endDate=models.DateField(null=True)
def __str__(self):
return self.title
class Status(models.Model):
name=models.CharField(max_length=250)
def __str__(self):
return self.name
And this is where these both function work:
{%extends 'base.html'%}
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>{{ form.as_table }}</table>
<input type="submit" value="Submit">
{# <input type="submit" onclick="window.location='{% url 'project:all' %}' ; return false;" value="Cancel">#}
</form>
{% endblock %}
How can i separate each element of this form and put it in a better designed page?
Thanks
There are two ways:
Option 1:
Loop over the form fields and render them individually:
{% for field in form %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<span class="form-text">{{ field.help_text|safe }}</span>
{% endif %}
</div>
{% endfor %}
See docs for more.
Option 2:
You can manually create form inputs and give them the correct field name attribute. This gives you more control but also requires more work:
<div class="form-group"
<input
type="text"
name="title"
value="{{ form.title.value }}"
class="form-control {% if form.title.errors %}is-invalid{% endif %}"
>
{% if form.title.help_text%}
<span class="form-text">{{ form.title.help_text|safe }}</span>
{% endif %}
<div class="invalid-feedback">{{ form.title.errors }}</div>
</div>
<!-- now do the same for other fields -->

ListView queryset Can not pass context data to Template

I am having trouble with my Search API. Results of the queryset could not get through my template even though the query set fetched data from the model.
If the search is empty the queryset should return all the Models associated to the current project, otherwise, it should return models that qualify the criteria in the query.
I have tested the result of the query and it returns records from the model but could not display the instances into the template.
My SEARCH ListView:
class ModelSearchListView(ListView):
model = Model
template_name = 'predictions/model_listview.html'
context_object_name = 'models'
paginate_by = 2
def get_queryset(self):
query = self.request.GET.get('q')
proj_pk = self.kwargs.get('pk')
proj = get_object_or_404(Project, id=proj_pk)
if query:
result = Model.objects.filter(Q(project=proj.id) & (Q(name__contains=query) |
Q(algorithm_type__contains=query) |
Q(predictors__contains=query) |
Q(target_column__contains=query))).order_by('-date_created')
# print('result: ', result)
else:
result = Model.objects.filter(project=proj.id).order_by('-date_created')
print('result: ', result)
return result
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
project = Project.objects.filter(id=self.kwargs.get('pk')).first()
context['current_project'] = project.id
MY SEARCH FORM:
<form class="form my-2 my-lg-0" method="GET"
action="{% if current_project %}
{% url 'model-search-listview' current_project %}
{% else %}
{% url 'model-search-listview' object.project.id %}
{% endif %}">
<div class="input-group">
<input class="form-control " type="text" name="q" value="{{ request.GET.q }}" aria-label="Search"
placeholder="Search">
<span class="input-group-btn">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit" value="Search">
Search
</button>
</span>
</div>
</form>
The TEMPLATE:
{% if not models %} #Always TRUE because models is empty
<h5>No prediction models created for this project!</h5>
{% else %}
#Loop never executed
{% for model in models %} # models HERE ALWAYS returns empty
[SOME CODE HERE]
{% endfor %}
{% endif %}
You need to return the new context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
project = Project.objects.filter(id=self.kwargs.get('pk')).first()
context['current_project'] = project.id
return context

Django: ModelFormSet saving first entry only

Update:
The issue seemed to be in the coding for Django-formset. I was processing it as an inline formset and not a model formset. The answer below was also correct. Thanks!
I am working with a model formset for an intermediate model. I am using django-formset js to add additional formset fields on the template. Most everything works OK except that when I go to save the formset only the first entry is being saved to the DB. The first entry is saved and assigned correctly but any after than just disappear. It is not throwing any errors so I am not sure what is going wrong. Thanks!
The Model
class StaffAssignment(models.Model):
study = models.ForeignKey(Study, related_name='study_set', null=True, on_delete=models.CASCADE)
staff = models.ForeignKey('account.UserProfile', related_name='assigned_to_set', null=True, on_delete=models.CASCADE)
role = models.CharField(max_length=100, null=True)
assigned_on = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-role',)
def __str__(self):
return '{} is assigned to {}'.format(self.staff, self.study)
The Form:
class AddStaff(forms.ModelForm):
model = StaffAssignment
fields = ('staff',)
def __init__(self, *args, **kwargs):
super(AddStaff, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control'})
The View:
def add_staff(request, study_slug):
study = get_object_or_404(Study, slug=study_slug)
staff_formset = modelformset_factory(StaffAssignment, form=AddStaff, fields=('staff',), can_delete=True)
if request.method == 'POST':
staffList = staff_formset(request.POST, request.FILES)
if staffList.is_valid():
for assignment in staffList:
assigned = assignment.save(commit=False)
assigned.study = study
assigned.role = assigned.staff.job_title
assigned.save()
return HttpResponseRedirect(reverse('studies:studydashboard'))
else:
HttpResponse('Something is messed up')
else:
staffList = staff_formset(queryset=StaffAssignment.objects.none())
return render(request, 'studies/addstaff.html', {'staffList': staffList, 'study': study})
The Template:
<form action="{% url 'studies:addstaff' study.slug %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="box-body">
{% for list in staffList %}
<div class="form-group" id="formset">
{% if list.instance.pk %}{{ list.DELETE }}{% endif %}
{{ list.staff }}
{% if list.staff.errors %}
{% for error in list.staff.errors %}
{{ error|escape }}
{% endfor %}
{% endif %}
</div>
{% endfor %}
{{ staffList.management_form }}
</div>
<div class="box-footer">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
You are not including the primary key field in the template, as required by the docs. Add
{% for list in staffList %}
{{ list.pk }}
...
{% endfor %}

Django Rendering template objects error

I have a problem with the way I structured my function.
My function renders 2 forms. A form to change the primary picture for a whiteboard and a form to delete a picture from a particular whiteboard
Both forms displays a dropbox that list all whiteboards for the students to pick and when the students pick a whiteboard . it's displays all the objects.
The form that set a primary picture for a whiteboard works perfectly because it display all picture objects but when I choose a value from the dropbox under the delete picture.
The function doesn't return all the pictures objects underneath the delete picture header but it instead displays all the picture under the primary forms.
I think the problem is with my if forms.is_valid(): and my if formss.is_valid(): because when a form is submitted . It only get POST into the if forms.is_valid():
my views.py
def WhiteBoardEditor(request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('world:LoginRequest'))
picture = {}
pict = {}
if request.method == "POST":
forms = BoardPictureForm(request.user,request.POST,)
formss = PictureDeleteForm(request.user,request.POST,)
if forms.is_valid():
board = forms.cleaned_data['board']
if board:
boards = forms.cleaned_data['board']
picture = Picture.objects.filter(board=boards)
return render(request,'boardeditor.html',{'picture':picture,'boardpicture':BoardPictureForm(request.user),'picturedelete':PictureDeleteForm(request.user)})
if formss.is_valid():
pooh = formss.cleaned_data['board']
if pooh:
pooh = formss.cleaned_data['board']
pict = Picture.objects.filter(board=pooh)
return render(request,'boardeditor.html',{'pict':pict,'boardpicture':BoardPictureForm(request.user),'picturedelete':PictureDeleteForm(request.user)})
return render(request,'boardeditor.html',{'boardpicture':BoardPictureForm(request.user),'picturedelete':PictureDeleteForm(request.user)})
my boardeditor.html
<h1> Set a primary picture for a whiteboard</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ boardpicture.as_p }}
<input type = "submit" value= "save" />
</form>
{% for p in picture %}
<li>{{p.description}}
{% endfor %}
<h1> Delete picture from whiteboard</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ picturedelete.as_p }}
<input type = "submit" value= "save" />
</form>
</form>
{% for pi in pict %}
{ pi.description }}
{% endfor %}
my forms.py
class BoardPictureForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(BoardPictureForm, self).__init__(*args, **kwargs)
self.fields['board'].queryset = Board.objects.filter(user=user)
class Meta:
model = Picture
fields = ('board',)
class PictureDeleteForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(PictureDeleteForm, self).__init__(*args, **kwargs)
self.fields['board'].queryset = Board.objects.filter(user=user)
class Meta:
model = Picture
fields = ('board',)
Your two forms use the same field which is the board. That's why when you submit the second form, the first form is the one process.
To fix your problem, you need to specify the action in every form. Notice that in my answer, I add input in hidden format with the process value. And in your view, I create if and else statement for that process so that when you submit the form the system will know which form must be executed.
if request.method == "POST":
forms = BoardPictureForm(request.user,request.POST,)
formss = PictureDeleteForm(request.user,request.POST,)
if request.POST['process'] == 'primary':
if forms.is_valid():
board = forms.cleaned_data['board']
if board:
boards = forms.cleaned_data['board']
picture = Picture.objects.filter(board=boards)
return render(request,'boardeditor.html',{
'picture':picture,
'boardpicture':BoardPictureForm(request.user),
'picturedelete':PictureDeleteForm(request.user)
})
elif request.POST['process'] == 'delete':
if formss.is_valid():
pooh = formss.cleaned_data['board']
if pooh:
pooh = formss.cleaned_data['board']
pict = Picture.objects.filter(board=pooh)
return render(request,'boardeditor.html',{
'pict':pict,
'boardpicture':BoardPictureForm(request.user),
'picturedelete':PictureDeleteForm(request.user
)}
elif request.POST['process'] == 'third':
//other form here
<h1> Set a primary picture for a whiteboard</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ boardpicture.as_p }}
<input type = "hidden" name="process" value= "primary" />
<input type = "submit" value= "save" />
</form>
{% for p in picture %}
<li>{{p.description}}
{% endfor %}
<h1> Delete picture from whiteboard</h1>
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ picturedelete.as_p }}
<input type = "hidden" name="process" value= "delete" />
<input type = "submit" value= "save" />
</form>
</form>
{% for pi in pict %}
{ pi.description }}
{% endfor %}
Edit2: Ah, I've misunderstood this question - the following isn't relevant to the question, but still useful to the OP
I suspect it's because your not generating well-formed HTML. Try changing the "picture" part in boardeditor.html to be
<ul>
{% for p in picture %}
<li>{{p.description}}</li>
{% endfor %}
</ul>
Edit: also,
{% for pi in pict %}
{{ pi.description }} <!-- added an opening curly brace -->
{% endfor %}
And you have a redundant </form> towards the bottom