formset_factory with forms with different initial data - django

I am writing a view that uses POST data to display multiple forms with differing prefilled FK's
I have a ModelForm in forms.py
class SurveyForm(forms.ModelForm):
class Meta:
model = Survey
who's model looks like this...
class Survey(models.Model):
student = models.ForeignKey(Student)
surveyset = models.ForeignKey(SurveySet)
cei_0 = models.BooleanField()
cei_1 = models.BooleanField()
My view looks kind of like this so far
# ... after building a list from POST we essentially have:
list_of_studentids = [1,3,2,6,7,45]
students = []
for i in list_of_student_ids:
students.append(Student.objects.filter(id=i))
SurveyFormSet = formset_factory(SurveyForm, extra=6)
formset = SurveyFormSet(initial=[
{'surveyset': SurveySet.create(),
'student': ?????,}
])
How do I return a bunch of forms with different student FK's and the same surveyset FK?

You need to pass an instance attribute to the form:
prefilled_survey = Survey(student=student_instance, surveyset=surveyset_instance)
form = SurveyForm(request.POST or None, instance=prefilled_survey)

Related

Calculating values from different Django Models

I have 2 similar django models, the difference between them is that one of them has a Foreign key with another model and the other is like a general Model.
class Project:
name = models.CharField(default='',max_length=100,verbose_name="name")
class Article(models.Model):
code = models.CharField(default='',max_length=20,verbose_name="Norma")
name = models.TextField(default='',verbose_name="Denumire")
project = models.ForeignKey(Project,on_delete=models.CASCADE,null=True)
quantity = models.FloatField(default=0,verbose_name="Quantity")
value = models.FloatField(default=0,verbose_name="Value")
class BaseArticle(models.Model):
code = models.CharField(default='',max_length=20,verbose_name="Norma")
name = models.TextField(default='',verbose_name="Denumire")
price = models.FloatField(default=0,verbose_name="Price")
I want to calculate the value attribute from Article model, it should be like this:
if article.code == basearticle.code:
article.value = article.quantiy * basearticle.price
How should I do this type of calculations? Should I write the logic in views.py?
views.py:
class ProjectDetail(LoginRequiredMixin, DetailView):
template_name = "proiecte/project_detail.html"
context_object_name = "projects"
model = Project
In the template I use {% for article in projects.article_set.all %} to generate a table that has all the attributes as headers.
ok based on your understanding you can do this way
class Article(models.Model):
code = models.CharField(default='',max_length=20,verbose_name="Norma")
name = models.TextField(default='',verbose_name="Denumire")
project = models.ForeignKey(Project,on_delete=models.CASCADE,null=True)
quantity = models.FloatField(default=0,verbose_name="Quantity")
value = models.FloatField(default=0,verbose_name="Value")
def clean(self):
values =Article.objects.get(code='P1').quantity*BaseArticle.objects.get(code =
Article.objects.get(code='P1')
return value
Since you can access your clean method anywhere as it is instance of class in views
class ProjectDetail(LoginRequiredMixin, DetailView):
template_name = "proiecte/project_detail.html"
context_object_name = "projects"
instance = Article.objects.get(pk=id)
value =instance.clean()
# your logic

Testing forms and widgets - Python

I trying to test the Django forms and widgets but it is returning false instead of true. The test looks right but not sure.I have added the test form, interest form and player class
class TestForm(TestCase):
def test_player_interests_fail(self):
form = PlayerInterestsForm(data={'interests': 'sport'})
self.assertEqual(form.is_valid(), True)
class PlayerInterestsForm(forms.ModelForm):
class Meta:
model = Player
fields = ('interests', )
widgets = {'interests': forms.CheckboxSelectMultiple}
class Player(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
quizzes = models.ManyToManyField(Quiz, through='TakenQuiz')
tournaments = models.ManyToManyField(Tournament, through='TakenTournament')
interests = models.ManyToManyField(Subject, related_name='interested_player')
def get_unanswered_questions(self, quiz):
answered_questions = self.quiz_answers\
.filter(answer__question__quiz=quiz)\
.values_list('answer__question__pk', flat=True)
questions = quiz.questions.exclude(pk__in=answered_questions).order_by('text')
return questions
def get_unanswered_tournament_questions(self, tournament):
answered_questions = self.tournament_answers\
.filter(answer__question__tournament=tournament)\
.values_list('answer__question__pk', flat=True)
questions = tournament.questions.exclude(pk__in=answered_questions).order_by('text')
return questions
def __str__(self):
return self.user.username
class Meta:
verbose_name_plural = "Player"
There are two issues with your code:
Player.interests is a ManyToManyField. That means the form expects the data to be a list of primary keys for the selected Subjects - passing a string value will not work. You have to pass integer IDs of Subject objects.
A CheckboxSelectMultiple allows multiple objects to be selected, which means you have to pass a list, not a single value.
So you need to do something like this:
def test_player_interests_fail(self):
# If you haven't already, you need to create a Subject
# I don't know how your Subject model is defined, but something like this
s = Subject.objects.create(name='Sport')
form = PlayerInterestsForm(data={'interests': [s.pk]})
self.assertEqual(form.is_valid(), True)

Filter queryset between two or more models for search form in django

Im building a search form in django, Im doing it with filter querysets, this form is like a "advance search form", I mean, form could has more than two inputs, but problem is that each input corresponding of a field of different model, I have this and works fine if the form only has one input for one model:
def post(self,request,*args,**kwargs):
buscar_predio = request.POST['nombre_predio']
query1 = InfoPredioGeneral.objects.filter(nombre_predio__iexact=buscar_predio)
if query1:
ctx = {'predio':query1}
return render(request,'resultados_busqueda.html',ctx)
else:
return render(request,'resultados_busqueda.html')
If I have this models:
class InfoPredioGeneral(models.Model):
nombre_predio = models.CharField(max_length=30)
class Propietario(models.Model):
predio = models.ForeignKey(InfoPredioGeneral,blank=True,null=True,related_name='predio_propietario+')
tipo_identificacion = models.ForeignKey(TipoIdentificacion,related_name='tipo identificacion+',blank=True,null=True)
In the post method how can I search in the same form a InfoPredioGeneral and Propietario? For example filter where nombre_predio is exact to "predio proof" and where tipo_identificacion is exacto to "123"? As you can see Propietario has a ForeignKey to InfoPredioGeneral
you are looking for a many-to-one relationship
https://docs.djangoproject.com/en/1.8/topics/db/examples/many_to_one/
you already set the related_name to "predio_propietario+" and with the '+' at the end this means there is no backwards relation to this model
there is you example:
queryset = Propietario.objects.filter(predio__nombre_predio__iexact=request.POST['nombre_predio'])
Extra:
class C(models.Model):
c_text = models.CharField(max_length=100)
class B(models.Model):
b_text = models.CharField(max_length=100)
class A(models.Model):
b = models.ForeignKey(B)
c = models.ForeignKey(C)
queryset will look like this:
queryset = A.objects.filter(b_btext__isexact="your b text", c_ctext__isexact="your c text")

Model Formset validation error

I want to use a model formset with a custom form. I am using a different view for the GET and a different one for the POST functions, cause my html page consists of three different model formsets. My model is one
class Image(models.Model):
customer = models.ForeignKey(Customer)
doctor = models.ForeignKey(Doctor)
date = models.DateField()
desc = models.CharField(max_length=30)
type = models.CharField(choices=TITLE,max_length=20)
image = models.ImageField(upload_to='/pictures/', verbose_name='Image')
XRAY=[
('---------','---------'),
('PA Ceph', 'PA Ceph'),
('Lateral Ceph', 'Lateral Ceph'),
('Panoramic', 'Panoramic'),
]
class XrayImageForm(ModelForm):
desc = forms.ChoiceField(choices=XRAY,required=True, widget=Select(attrs={"class":"form-control input-sm"}))
class Meta:
model = Image
exclude = ('customer', 'date','type', 'doctor',)
widgets = {
'desc':Select(attrs={'class':'form-control input-sm'}),
'date': TextInput(attrs={'class':'form-control input-sm datepicker input-append date',
'readonly':''}),
}
def save(self, commit=True):
model = super(XrayImageForm, self).save(commit=False)
model.desc = self.cleaned_data['desc'];
if commit:
model.save()
return model
class InternalImageForm(ModelForm):
desc = form.ChoiceField(....) # I have to do this cause different ModelForm has different choices in the desc field
class Meta:
model = Image
exclude = ('customer',)
My get view is the following
def images(request, customer_id):
images = Image.objects.all().order_by('date')
pictures = {}
for image in images:
date = image.date.stformat("%d/%M/%Y")
if not pictures.has_key(date):
pictures[date] = {image.title:[image,]}
else:
if pictures[date].has_key(image.title):
pictures[date][image.title].append(image)
else:
pictures[date] = {image.title:[image,]}
xray_formset = modelformset_factory(Image, form=XrayImageForm,extra=4)
xray_formset(queryset=Image.objects.none())
internal_form = InternalImageForm()
external_form = ExternalImageForm()
args = dict(pictures=pictures, xray_formset=xray_formset, internal_form=internal_form, external_form=external_form, customer_id=customer_id)
return render_to_response('customer/images.html', args, context_instance=RequestContext(request))
I want to have them filtered by date and each date by a title (different Images could have the same title, and same date)
my post view
def upload_xray(request, customer_id):
customer = Customer.objects.get(pk=customer_id)
if request.method == 'POST':
XrayFormSet = modelformset_factory(Image, form=XrayImageForm, extra=4)
xray_formset = XrayFormSet(request.POST, request.FILES)
print xray_formset
return redirect('customer-images', customer_id=customer_id)
But when I post the data i get a
ValidationError
Exception Value:[u'ManagementForm data is missing or has been tampered with']
I don't do any actual saving, just wanted to see if it works. Also All fields are required but i don't fill all fields in the formset on my page (Suppose the user can upload 4 pictures but he might not want to. ). Hope I am making a bit of sense...Why is that error?
Make sure you are including the management form in your template.
{{ xray_formset.management_form }}
If that doesn't work, then update your question to include your template.

field choices() as queryset?

I need to make a form, which have 1 select and 1 text input. Select must be taken from database.
model looks like this:
class Province(models.Model):
name = models.CharField(max_length=30)
slug = models.SlugField(max_length=30)
def __unicode__(self):
return self.name
It's rows to this are added only by admin, but all users can see it in forms.
I want to make a ModelForm from that. I made something like this:
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=CHOICES),
}
but it doesn't work. The select tag is not displayed in html. What did I wrong?
UPDATE:
This solution works as I wanto it to work:
class ProvinceForm(ModelForm):
def __init__(self, *args, **kwargs):
super(ProvinceForm, self).__init__(*args, **kwargs)
user_provinces = UserProvince.objects.select_related().filter(user__exact=self.instance.id).values_list('province')
self.fields['name'].queryset = Province.objects.exclude(id__in=user_provinces).only('id', 'name')
name = forms.ModelChoiceField(queryset=None, empty_label=None)
class Meta:
model = Province
fields = ('name',)
Read Maersu's answer for the method that just "works".
If you want to customize, know that choices takes a list of tuples, ie (('val','display_val'), (...), ...)
Choices doc:
An iterable (e.g., a list or tuple) of
2-tuples to use as choices for this
field.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
ModelForm covers all your needs (Also check the Conversion List)
Model:
class UserProvince(models.Model):
user = models.ForeignKey(User)
province = models.ForeignKey(Province)
Form:
class ProvinceForm(ModelForm):
class Meta:
model = UserProvince
fields = ('province',)
View:
if request.POST:
form = ProvinceForm(request.POST)
if form.is_valid():
obj = form.save(commit=True)
obj.user = request.user
obj.save()
else:
form = ProvinceForm()
If you need to use a query for your choices then you'll need to overwrite the __init__ method of your form.
Your first guess would probably be to save it as a variable before your list of fields but you shouldn't do that since you want your queries to be updated every time the form is accessed. You see, once you run the server the choices are generated and won't change until your next server restart. This means your query will be executed only once and forever hold your peace.
# Don't do this
class MyForm(forms.Form):
# Making the query
MYQUERY = User.objects.values_list('id', 'last_name')
myfield = forms.ChoiceField(choices=(*MYQUERY,))
class Meta:
fields = ('myfield',)
The solution here is to make use of the __init__ method which is called on every form load. This way the result of your query will always be updated.
# Do this instead
class MyForm(forms.Form):
class Meta:
fields = ('myfield',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the query here
MYQUERY = User.objects.values_list('id', 'last_name')
self.fields['myfield'] = forms.ChoiceField(choices=(*MYQUERY,))
Querying your database can be heavy if you have a lot of users so in the future I suggest some caching might be useful.
the two solutions given by maersu and Yuji 'Tomita' Tomita perfectly works, but there are cases when one cannot use ModelForm (django3 link), ie the form needs sources from several models / is a subclass of a ModelForm class and one want to add an extra field with choices from another model, etc.
ChoiceField is to my point of view a more generic way to answer the need.
The example below provides two choice fields from two models and a blank choice for each :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=([['','-'*10]]+[[x.id, x.__str__()] for x in Speakers.objects.all()]))
event = forms.ChoiceField(choices=( [['','-'*10]]+[[x.id, x.__str__()] for x in Events.objects.all()]))
If one does not need a blank field, or one does not need to use a function for the choice label but the model fields or a property it can be a bit more elegant, as eugene suggested :
class MixedForm(forms.Form):
speaker = forms.ChoiceField(choices=((x.id, x.__str__()) for x in Speakers.objects.all()))
event = forms.ChoiceField(choices=(Events.objects.values_list('id', 'name')))
using values_list() and a blank field :
event = forms.ChoiceField(choices=([['','-------------']] + list(Events.objects.values_list('id', 'name'))))
as a subclass of a ModelForm, using the one of the robos85 question :
class MixedForm(ProvinceForm):
speaker = ...