My application has two models, Recipe and RecipeIngredient, related by foreign key. Each Recipe may have many different RecipeIngredients (and different number of associated ingredients for each recipe). So using a dynamic formset to assign ingredients to a recipe seems to be the best approach.
I have a simple form that has a user enter a quantity and select a unit and ingredient from a dropdown. As a standalone form it works fine.
Unfortunately, I cannot get any formset incorporating it to submit.
I have searched many of the different formset Q&As here on StackOverflow but cannot see where the problem is. When I click 'submit', the form blinks and refreshes in place, with the entered values still in the form. No objects are created. No errors report to the console nor to the browser. The related code:
The models:
class RecipeBase(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField(max_length=128,null=False)
creation_date = models.DateField("Record creation date",auto_now_add=True)
creation_user = models.CharField("Record creation user",max_length=150)
lastupdated_date = models.DateField(auto_now=True)
lastupdated_user = models.CharField(max_length=150)
category = models.ForeignKey(RecipeCategory,on_delete=models.CASCADE,related_name='recipes')
subcategory = models.ForeignKey(RecipeSubcategory,on_delete=models.CASCADE,related_name='recipes')
def __str__(self):
return str(self.name)
class RecipeIngredient(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
quantity = models.DecimalField(max_digits=8,decimal_places=3,null=False)
referenced_unit = models.ForeignKey(UnitDetail,on_delete=models.CASCADE)
referenced_ingredient = models.ForeignKey(Ingredient,on_delete=models.CASCADE)
parent_recipe = models.ForeignKey(RecipeBase,on_delete=models.CASCADE,related_name='ingredients')
def __str__(self):
return str(self.id)[:8]
The call to enter ingredients:
<a class='btn btn-success' href="{% url 'recipes:addingredient' pk=recipe_details.pk %}">Add Ingredients</a>
Calls the view:
class AddIngredientView(CreateView):
form_class = AddIngredientForm
model = RecipeIngredient
template_name = 'addingredientmultiple2.html'
success_url = "recipes:listrecipes"
def get_context_data(self, **kwargs):
parent_recipe_id = RecipeBase.objects.get(id=self.kwargs['pk'])
data = super(AddIngredientView, self).get_context_data(**kwargs)
if self.request.POST:
data['ingredients'] = AddIngredientFormset(self.request.POST)
data['parent_recipe_id'] = parent_recipe_id
else:
data['ingredients'] = AddIngredientFormset()
data['fixture'] = parent_recipe_id
return data
def form_valid(self, form, **kwargs):
user = self.request.user
fixture = RecipeBase.objects.get(id=self.kwargs['pk'])
context = self.get_context_data()
formset = AddIngredientFormset(self.request.POST)
if formset.is_valid():
ingredients = formset.save()
for recipeingredient in ingredients:
#recipeingredient.creation_user = str(request.user)
#recipeingredient.lastupdated_user = str(request.user)
recipeingredient.parent_recipe_id = parent_recipe_id
#recipeingredient.user = user
recipeingredient.save()
return super(AddIngredientView, self).form_valid(form)
Using form and formset:
class AddIngredientForm(forms.ModelForm):
quantity = forms.DecimalField(widget=forms.NumberInput(attrs={'data-placeholder': 0.00,'size': '8','label_tag': ''}))
referenced_unit = forms.ModelChoiceField(queryset=UnitDetail.objects.all())
referenced_ingredient = forms.ModelChoiceField(queryset=Ingredient.objects.all())
class Meta():
model = RecipeIngredient
fields = ('quantity','referenced_unit','referenced_ingredient',)
AddIngredientFormset = inlineformset_factory(RecipeBase,RecipeIngredient,form=AddIngredientForm,extra=1,can_delete=True)
And template:
{{ ingredients.media.css }}
<div class="container">
<h2>Add ingredients to {{ referring_recipe_name }}</h2>
<h4>{{ referring_recipe_id }}</h4>
</div>
<div class="col-md-4">
<form class="form-horizontal" enctype="multipart/form-data" method="post">{% csrf_token %}
<table class="table">
{{ ingredients.management_form }}
{% for form in ingredients.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input class='btn btn-success' type="submit" value="Save"/> back to the list
</form>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'formset/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row').formset({
addText: 'ADD INGREDIENT',
deleteText: 'REMOVE',
prefix: 'ingredients'
});
</script>
{{ ingredients.media.js }}
Related
I've created View which filters data by search query given in textbox. As well as I used Paginator to show data divided into pages.
My problem is, when I filter data with Q object then and try to paginate by clicking the next button, all data is refreshed.
When I search text by Q object the URL becomes http://127.0.0.1:8000/mael/parties/?q=keyword
And from clicking the next button the URL becomes http://127.0.0.1:8000/mael/parties/?page=2
When I manually change URL http://127.0.0.1:8000/mael/parties/?q=keyword&page=2, then it works. But I don't know how to do this in code.
Is it possible to use Q object search and pagination together?
My View
from mael.models import PartyTotalBillsView
from django.views.generic import ListView
from django.db.models import Q
from django.http import HttpResponseRedirect
class PartyListView(ListView):
paginate_by = 2
model = PartyTotalBillsView
def parties(request):
# Show all records or searched query record
search_text = request.GET.get('q','')
try:
if search_text:
queryset = (Q(party_name__icontains=search_text))
party_list = PartyTotalBillsView.objects.filter(queryset).order_by('party_name')
else:
# Show all data if empty keyword is entered
party_list = PartyTotalBillsView.objects.order_by('party_name')
except PartyTotalBillsView.DoesNotExist:
party_list = None
paginator = Paginator(party_list, 2) # Show 2 rows per page: for Test
page_number = request.GET.get('page')
party_list = paginator.get_page(page_number)
return render(request, 'mael/parties.html', {'party_list': party_list})
Template file
<form id="search-form" method="get" action="/mael/parties/">
<input id="search-text" type="text" name="q" placeholder="Enter search keyword">
<input class="btn-search-party" type="submit" value="Search" />
</form>
<br/>
<table class="show-data">
<thead>
<tr>
<th>ID</th>
<th>Party Name</th>
<th>Total Bill Amount</th>
<th>Phone</th>
<th>Address</th>
<th></th>
</tr>
</thead>
{% if party_list %}
<tbody>
{% for party in party_list %}
<tr>
<td class="party-id">{{ party.party_id }}</td>
<td class="party-name">{{ party.party_name }}</td>
<td>{{ party.total_bills }}</td>
<td class="party-phone">{{ party.party_phone }}</td>
<td class="party-address">{{ party.party_address }}</td>
<td>
<button class="btn-modify" data-partyid="{{party.party_id}}" type="buttton">
Modify
</button>
</td>
</tr>
{% endfor %}
</tbody>
{% endif %}
</table>
<div class="pagination">
<span class="step-links">
{% if party_list.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ party_list.number }} of {{ party_list.paginator.num_pages }}
</span>
{% if party_list.has_next %}
next
last »
{% endif %}
</span>
</div>
Please do not use two views. A ListView can perform filtering as well:
class PartyListView(ListView):
paginate_by = 2
model = PartyTotalBillsView
template_name = 'mael/parties.html'
context_object_name = 'party_list'
def querystring(self):
qs = self.request.GET.copy()
qs.pop(self.page_kwarg, None)
return qs.urlencode()
def get_queryset(self):
qs = super().get_queryset()
if 'q' in self.request.GET:
qs = qs.filter(party_name__icontains=self.request.GET['q'])
return qs.order_by('party_name')
In the links for the previous and next pages, you then append the querystring of the view:
<span class="step-links">
{% if party_list.has_previous %}
« first
previous
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if party_list.has_next %}
next
last »
{% endif %}
</span>
Pagination & CBV
If you are using django generic ListView with paginate_by attribute, you don't need to build paginator instance. Either you use CBV (Class Based View) or Function Views but not both.
For HTML display create a _django_pager.html page to include in your list pages.
{% comment %}
https://getbootstrap.com/docs/4.1/components/pagination/
{% endcomment %}
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?{% url_replace page=page_obj.previous_page_number %}">«</a></li>
{% else %}
<li class="page-item disabled">«</li>
{% endif %}
{% for i in page_obj.paginator.page_range %}
{% if page_obj.number == i %}
<li class="page-item active">{{ i }}<span class="sr-only">(current)</span></li>
{% else %}
<li class="page-item"><a class="page-link" href="?{% url_replace page=i %}">{{ i }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?{% url_replace page=page_obj.next_page_number %}">»</a></li>
{% else %}
<li class="page-item disabled">»</li>
{% endif %}
</ul>
{% endif %}
Filtering
Q object is powerfull for building complex query but you have to specify DB field under which Q object applies.
Instead of hardcoding the form in HTML I recommand to use a Form Class. So in forms.py create a PartySearchForm
class PartySearchForm(forms.Form):
"""
Search in party
"""
search_text = forms.CharField(max_length=100,
required=False,
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": "Search"
})
)
Option 1: filter queryset in view
class PartyListView(ListView):
model = PartyTotalBillsView
form = PartySearchForm
paginate_by = 100
def build_where(self):
where = Q(pk__gt=0)
if self.request.GET.get("search_text"):
search_list = self.request.GET.get("search_text", None).split()
for search_item in search_list:
where &= (
Q(party_name__icontains=search_item)
)
return where
def get_queryset(self):
qs = self.model.objects.all()
qswhere = qs.filter(self.build_where())
# first param must be request.GET or None (essential for the first load and initial values)
# https://www.peterbe.com/plog/initial-values-bound-django-form-rendered
self.form = PartySearchForm(self.request.GET or None)
return qswhere
In the build_where function you can add as many search field as you want. You can search on other DB field than party_name by adding the fields to the where variable.
where &= (
Q(party_name__icontains=search_item)
| Q(party_location__icontains=search_item)
)
You can also add other search fields than search_text in your form and add Q search on the where variable.
if self.request.GET.get("my_new_field"):
where &= Q(supplier=self.request.GET.get("my_new_field", ""))
Key point here is the get_queryset method where the displayed queryset is defined, ie: fetched, filtered and sorted (which could also be a method). .order_by('party_name') is not useful if you add a class Meta in models.py
class Meta:
verbose_name = "Let's go party"
ordering = ['party_name']
One other way to do would be to pass the queryset to the form and perform the search
Option 2: filter queryset in form
Looks even cleaner with the search logic in the SearchForm only!
PartyListView.get_queryset become
def get_queryset(self):
qs1 = self.model.objects.all()
self.form = PartySearchForm(self.request.GET, queryset=qs1)
qs = self.form.get_queryset(self.request.GET)
return qs
PartySearchForm become
class PartySearchForm(forms.Form):
"""
Search in party
"""
search_text = forms.CharField(max_length=100,
required=False,
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": "Search"
})
)
def __init__(self, *args, **kwargs):
"""
Takes an option named argument ``queryset`` as the base queryset used in
the ``get_queryset`` method.
"""
self.queryset = kwargs.pop("queryset", None)
super().__init__(*args, **kwargs)
def get_queryset(self, request):
where = Q(pk__gt=0)
# is_valid() check is important to get access to cleaned_data
if not self.is_valid():
return self.queryset
search_text = self.cleaned_data.get("search_text").strip()
if search_text:
search_list = search_text.split()
for search_item in search_list:
where &= (
Q(party_name__icontains=search_item)
)
qs = self.queryset.filter(where)
return qs.distinct()
Eventually, if you are using Postgres DB and want to go deeper with Text Search you can implement Django full text search. Pros & cons can be gained by reading this.
For one of my open source projects, I need to create ONE add/edit page in order to make possible to edit several records with one save.
The repo is an IMDB clone formed for learning purpose. A user can add her/his favorite genres in her/his profile. Then an edit page is formed to show the list of those favored genres and the movies within that genre. (A for loop here) User can add notes, watch list options and so on to those movies. (NOT a FORMSET)
However, the code doesn't work as expected. The page cannot be saved and only the first checkbox of the list can be changed.
There is no error.
NOTE:
You can install repo with dummy data.
(https://github.com/pydatageek/imdb-clone)
Then after logging in, select your favorite genres. (http://localhost:8000/users/profile/)
Then (I wish it can be solved here) you can see the movies with your selected genres. Add notes, to watch list... (http://localhost:8080/users/profile/movies2/)
# users/templates/user-movies-with_loop.html
{% extends 'base.html' %}{% load crispy_forms_tags %}
<!-- Title -->
{% block htitle %}Your movies from favorite genres{% endblock %}
{% block title %}Your movies from favorite genres{% endblock %}
{% block content %}
<div class="card card-signin">
{% include 'users/profile-menu.html' %}
<h3 class="card-title text-center my-4">Take notes for your movies <small></small></h3>
<hr class="mb-1">
<div class="card-body">
<form method="POST">
{% csrf_token %}
{% for genre in user.genres.all %}
<h2 for="genre" name="genre" value="{{ genre.id }}">{{ genre.name }}</h2>
{% for movie in genre.movies.all %}
<div class="ml-5">
<h4>{{ movie.title }}</h4>
{{ form|crispy }}
</div>
<input type="hidden" name="user" value="{{ user.id }}">
<input type="hidden" name="movie" value="{{ movie.id }}">
{% empty %}
<p class="alert alert-danger">The genre you have selected on your profile doesn't have any movies!</p>
{% endfor %}
{% empty %}
<p class="alert alert-danger">You should select genres with movies from your profile to edit here!</p>
{% endfor %}
<input class="btn btn-lg btn-primary btn-block text-uppercase" type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}
# users.forms.py
...
class UserMovieFormWithLoop(ModelForm):
genre = forms.HiddenInput(attrs={'disabled': True})
class Meta:
model = UserMovie
fields = ('user', 'movie', 'note', 'watched', 'watch_list')
widgets = {
'user': forms.HiddenInput,
'movie': forms.HiddenInput,
'watched': forms.CheckboxInput(),
}
...
# users.models.py
...
class UserMovie(models.Model):
"""
Users have notes about their favorite movies.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE)
movie = models.ForeignKey('movies.Movie', default=1, on_delete=models.CASCADE)
note = models.CharField(max_length=250, null=True, blank=True)
watched = models.BooleanField(default=False, verbose_name='Have you seen before?')
watch_list = models.BooleanField(default=False, verbose_name='Add to Watch List?')
def __str__(self):
return f'{self.user.username} ({self.movie.title})'
...
# users.views.py
...
class UserMovieViewWithLoop(LoginRequiredMixin, CreateView):
model = UserMovie
template_name = 'users/user-movies-with_loop.html'
form_class = UserMovieFormWithLoop
success_message = 'your form has been submitted.'
success_url = reverse_lazy('users:user_movies2')
def form_valid(self, form):
user = self.request.user
movie_counter = Movie.objects.filter(genres__in=user.genres.all()).count()
f = form.save(commit=False)
f.user = user
for i in range(movie_counter):
f.pk = None
f.save()
return super().form_valid(form)
def get_context_data(self, **kwargs):
context = super(UserMovieViewWithLoop, self).get_context_data(**kwargs)
context['form'] = self.form_class
return context
def get_object(self):
user = self.request.user
return UserMovie.objects.get(user=user)
...
I'm trying to expand the Django tutorial for a school project and make it into a more usable voting app.
What I want is to allow users to create Polls and invite other registered users by email to vote on their Poll. Only the invited users will be allowed to vote.
My models.py:
class Poll(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published', auto_now_add=True)
is_active = models.BooleanField(default=True)
activation_date = models.DateTimeField('Activation Date', blank=True, null=True)
expiration_date = models.DateTimeField('Expiration Date', blank=True, null=True)
public_key = models.CharField(max_length=30, blank=True)
hash = models.CharField(max_length=128, blank=True)
timestamp = models.DateTimeField(auto_now=True)
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
class Choice(models.Model):
question = models.ForeignKey(Poll, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class EligibleVoters(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
poll = models.ForeignKey(Poll, on_delete=models.CASCADE)
email = models.EmailField(null=True)
I have a Poll table which contains the Poll Title and other information regarding the Poll. I also created a separate Choices table (like in the tutorial) which has a ForeignKey to the Poll table and contains the poll choices.
I figured that in order to invite users as eligible voters I needed a third table with ForeignKeys to the Poll and User tables. So i created one.
I should note that I'm using the build-in user model.
Here's my views.py:
class NewPoll(CreateView):
model = Poll
fields = ['question_text', 'is_active', 'activation_date', 'expiration_date']
success_url = reverse_lazy('voting:index')
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
if self.request.POST:
data['choices'] = ChoiceFormSet(self.request.POST)
# data['eligible_voters_poll'] = EligibleVotersFormSetPoll(self.request.POST)
# data['eligible_voters_user'] = EligibleVotersFormSetUser(self.request.POST)
#data['eligible_voters'] = EligibleVotersFormSet(self.request.POST)
else:
data['choices'] = ChoiceFormSet()
# data['eligible_voters_poll'] = EligibleVotersFormSetPoll()
# data['eligible_voters_user'] = EligibleVotersFormSetUser()
# data['eligible_voters'] = EligibleVotersFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
choices = context['choices']
# eligible_voters_poll = context['eligible_voters_poll']
# eligible_voters_user = context['eligible_voters_user']
#eligible_voters = context['eligible_voters']
with transaction.atomic():
self.object = form.save()
if choices.is_valid():
choices.instance = self.object
choices.save()
# if eligible_voters_poll.is_valid() and eligible_voters_user.is_valid():
# eligible_voters_poll.instance = self.object
# eligible_voters_poll.save()
# eligible_voters_user.instance = self.object
# eligible_voters_user.save()
#if eligible_voters.is_valid():
# eligible_voters.instance = self.object
# eligible_voters.save()
return super().form_valid(form)
I have commented the lines that are previous attempt into making it work. Without the commented lines the user is able to create a poll and also create choices. I'm having trouble making the invite part working though.
Here is my forms.py:
class PollForm(ModelForm):
activation_date = forms.DateTimeField(required=False)
expiration_date = forms.DateTimeField(required=False)
class Meta:
model = Poll
fields = ['question_text', 'is_active', 'activation_date', 'expiration_date']
class ChoiceForm(ModelForm):
class Meta:
model = Choice
exclude = ['votes']
ChoiceFormSet = inlineformset_factory(Poll, Choice, form=ChoiceForm, extra=1)
def form_maker(parent2):
class EligibleVotersForm(ModelForm):
# def __init__(self, user, poll, *args, **kwargs):
# self.user = user
# self.poll = poll
# super().__init__(*args, **kwargs)
def save(self, commit=True):
instance = super(EligibleVotersForm, self).save(commit=False)
# instance.parent1 = parent1
instance.parent2 = parent2
if commit:
instance.save()
return instance
class Meta:
model = EligibleVoters
fields = ['email']
return EligibleVotersForm
# EligibleVotersFormSetPoll = inlineformset_factory(Poll, EligibleVoters, form=EligibleVotersForm, extra=1)
# EligibleVotersFormSetUser = inlineformset_factory(User, EligibleVoters, form=EligibleVotersForm, extra=1)
# EligibleVotersFormSet = inlineformset_factory(Poll, EligibleVoters, form=form_maker(User), extra=1)
Here I have commented out again the lines that belong to my failed attempts at making it work. The closer I got it to working is to be able to invite by any email (not just the registered ones, which is what I want) and fill the EligibleVoters table with the emails associated with the poll_id. The user_id column though remained, empty.
Here's my html file:
{% extends 'base.html' %}
{% block content %}
{% load static %}
<h2>Poll Creation</h2>
<div class="col-md-4">
<form action='' method="post">
{% csrf_token %}
{{ form.as_p }}
<table class="table">
{{ choices.management_form }}
{% for form in choices.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %} formset_row1">
{% for field in form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<table class="table">
{{ eligible_voters.management_form }}
{% for form in eligible_voters.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle 'row1' 'row2' %} formset_row2">
{% for field in form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Save"/> back to the list
</form>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="{% static 'voting/js/jquery.formset.js' %}"></script>
<script type="text/javascript">
$('.formset_row1').formset({
addText: 'add poll choice',
deleteText: 'remove',
prefix: 'choice_set'
});
$('.formset_row2').formset({
addText: 'add eligible voter',
deleteText: 'remove',
prefix: 'eligiblevoters_set'
});
</script>
{% endblock %}
Any ideas on how to make it work and only allow he eligible voters to vote for the polls they are invited to?
I was thinking that maybe my EligibleVoters model is wrong somehow and I need a ManyToMany field somewhere??
I am driving to render a context scale that is not rendering in my HTML and I can manage to see the error. I do not get any error in the inspect/console and neither in the Atom terminal.
I am developing a survey app using a scale from 0-100% (using JavaScript)
but for some reason it is not rendering;
here is my code:
views.py
class SurveyDetail(View):
def get(self, request, *args, **kwargs):
survey = get_object_or_404(Survey, is_published=True, id=kwargs['id'])
if survey.template is not None and len(survey.template) > 4:
template_name = survey.template
else:
if survey.display_by_question:
template_name = 'survey/survey.html'
else:
template_name = 'survey/one_page_survey.html'
if survey.need_logged_user and not request.user.is_authenticated():
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
categories = Category.objects.filter(survey=survey).order_by('order')
form = ResponseForm(survey=survey, user=request.user,
step=kwargs.get('step', 0))
#try:
get_scale = form.get_multiple_scale()
#except:
# get_scale = None
context = {
'response_form': form,
'survey': survey,
'categories': categories,
'scales': get_scale
}
return render(request, template_name, context)
form.py:
class ResponseForm(models.ModelForm):
WIDGETS = {
Question.TEXT: forms.Textarea,
Question.SHORT_TEXT: forms.TextInput,
Question.RADIO: forms.RadioSelect,
Question.SELECT: forms.Select,
Question.SELECT_IMAGE: ImageSelectWidget,
Question.SELECT_MULTIPLE: forms.CheckboxSelectMultiple,
Question.SCALE: forms.TextInput,
}
class Meta(object):
model = Response
fields = ()
def __init__(self, *args, **kwargs):
""" Expects a survey object to be passed in initially """
self.survey = kwargs.pop('survey')
self.user = kwargs.pop('user')
try:
self.step = int(kwargs.pop('step'))
except KeyError:
self.step = None
super(ResponseForm, self).__init__(*args, **kwargs)
self.uuid = uuid.uuid4().hex
self.steps_count = len(self.survey.questions.all())
# add a field for each survey question, corresponding to the question
# type as appropriate.
data = kwargs.get('data')
for i, question in enumerate(self.survey.questions.all()):
is_current_step = i != self.step and self.step is not None
if self.survey.display_by_question and is_current_step:
continue
else:
try:
self.scales = question.get_multiple_scales()
except:
self.scales = None
self.add_question(question, data)
def get_multiple_scale(self):
mscale = []
for items in self.scales:
index, question = items
tag = "<p class='tagged'>{}</p>".format(question)
mscale.append(tag)
return mscale
HTML:
{% load bootstrap %}
{% load static %}
{% load i18n %}
{% load survey_extras %}
<table class="table">
<!--<thead>
<tr>
<th> Question </th>
<th> Answers </th>
</tr>
</thead> -->
<tbody>
{% for form in response_form %}
{% if form.field.widget.attrs.category == category.name or not form.field.widget.attrs.category %}
<tr class="{% if form.errors%} danger {% endif %}">
<td>
<div class="question-title">
<h4>{{ form.label|safe }}</h4>
</div>
{% if form.field.required %}
<span class="glyphicon glyphicon-asterisk" style="color:red"> </span>
{% endif %}
<span class="help-inline" style="color:red">
<strong> {% for error in form.errors %}{{ error }}{% endfor %} </strong>
</span> <br>
<div class="answers">
{% for field in form %}
<ul>
{{ field }}
</ul>
{% endfor%}
{% if "hidden" in form.field.widget.attrs %}
<br>
{% for scale in scales %}
{{ scale|safe }}
<div id="rate" class="scale">
</div>
<div class="scale-title">
<div class="container">
<div class="row">
<div class="col scaleleft">
0%
</div>
<div class="col scaleright">
100%
</div>
</div>
</div>
</div>
<br>
{% endfor %}
{% endif %}
</div>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
You've got a catch-all except clause in the get method. That is a very very very bad idea; you are catching and hiding any error that happens in the get_multiple_scale method. Probably, something is going wrong there, but your code makes it impossible to tell what.
Remove that try/except.
You have a similar one in your form's init method; there it makes even less sense, as you end up assigning None to self.scales which is the very thing that you're iterating over in get_multiple_scales. There is a very odd circular definition here, which you certainly shouldn't have.
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 %}