Django Forms Newbie Question - python

Alright, I'm at a loss with the Django Forms, as the documentation just doesn't seem to quite cover what I'm looking for. At least it seems to come to a screeching halt once you get past the most rudimentary of forms. I'm more than willing to take a link to good documentation, or a link to a good book that covers this topic, as an answer. Basically, this is how it breaks down, I have 3 models (quiz, questions, answers). I have 20 questions, with 4 potential answers (multi-choice), per quiz. The numbers can vary, but you get the point.
I need to create a form for these items, much like you'd expect in a multiple choice quiz. However, when I create the form by hand in the templates, rather than using django.forms, I get the following:
invalid literal for int() with base 10: 'test'
So I'm trying to mess with the django.forms, but I guess I'm just not grasping the idea of how to build a proper form out of those. Any help would be greatly appreciated, thanks.
For what it's worth here are the models:
class Quiz(models.Model):
label = models.CharField(blank=True, max_length=400)
slug = models.SlugField()
def __unicode__(self):
return self.label
class Question(models.Model):
label = models.CharField(blank=True, max_length=400)
quiz = models.ForeignKey(Quiz)
def __unicode__(self):
return self.label
class Answer(models.Model):
label = models.CharField(blank=True, max_length=400)
question = models.ForeignKey(Question)
correct = models.BooleanField()
def __unicode__(self):
return self.label

Yeah I have to agree the documentation and examples are really lacking here. The is no out of the box solution for the case you are describing because it goes three layers deep: quiz->question->answer.
Django has model inline formsets which solve the problem for two layers deep. What you will need to do to generate the form you want is:
Load up a quiz form (just a label text box from your model)
Load a an question formset: QuestionFormSet(queryset=Question.objects.filter(quiz=quiz))
For each question load up a answer formset in much the same way you load up the question formset
Make sure you save everything in the right order: quiz->question->answer, since each lower level needs the foreign key of the item above it

First, you create a ModelForm for a given Model. In this example I'm doing it for Quiz but you can rinse and repeat for your other models. For giggles, I'm making the "label" be a Select box with preset choices:
from django.models import BaseModel
from django import forms
from django.forms import ModelForm
CHOICES_LABEL = (
('label1', 'Label One'),
('label2', 'Label Two')
)
class Quiz(models.Model):
label = models.CharField(blank=True, max_length=400)
slug = models.SlugField()
def __unicode__(self):
return self.label
class QuizForm(ModelForm):
# Change the 'label' widget to a select box.
label = forms.CharField(widget=forms.Select(choices=CHOICES_LABEL))
class Meta:
# This tells django to get attributes from the Quiz model
model=Quiz
Next, in your views.py you might have something like this:
from django.shortcuts import render_to_response
from forms import *
import my_quiz_model
def displayQuizForm(request, *args, **kwargs):
if request.method == 'GET':
# Create an empty Quiz object.
# Alternately you can run a query to edit an existing object.
quiz = Quiz()
form = QuizForm(instance=Quiz)
# Render the template and pass the form object along to it.
return render_to_response('form_template.html',{'form': form})
elif request.method == 'POST' and request.POST.get('action') == 'Save':
form = Quiz(request.POST, instance=account)
form.save()
return HttpResponseRedirect("http://example.com/myapp/confirmsave")
Finally your template would look like this:
<html>
<title>My Quiz Form</title>
<body>
<form id="form" method="post" action=".">
<ul>
{{ form.as_ul }}
</ul>
<input type="submit" name="action" value="Save">
<input type="submit" name="action" value="Cancel">
</form>
</body>
</html>

Related

Django aggregation multiple filters

I have a Django project that allows users to create their own Projects and add multiple Skills to each Project.
I am trying to write a view that will allow me to display the name of each Skill as well as the Count of that Skill for all of that particular user Profile's Projects that are published.
For example, if a user has three projects where they've added the Skill "HTML" I'd like to show HTML (3) on their Profile, and so on.
Below is my current code which mostly works however it displays the count for that Skill from ALL user projects and not the specific user whose profile is being viewed.
Models:
#accounts/models.py
class Profile(models.Model):
#...
skills = models.ManyToManyField('skills.skill')
#projects/models.py
class Project(models.Model):
user = models.ForeignKey(User)
#...
published = models.BooleanField(default=False)
skills = models.ManyToManyField('skills.skill')
#...
#skills/models.py
class Skill(models.Model):
name = models.CharField(max_length=100)
Views:
#accounts/views.py
def profile(request, username):
user = get_object_or_404(User, username=username)
skill_counts = Skill.objects.annotate(num_projects=Count('project')).filter(project__user_id=user.id).order_by('-num_projects')[:10]
return render(request, 'accounts/profile.html', {
'user': user,
'skill_counts': skill_counts,
})
Template:
#accounts/templates/accounts/profile.html
{% for skill in skill_counts %}
<div class="skill-container">
<div class="label-skill">{{ skill.name }}</div> <div class="label-skill-count">
{{skill.project_set.count}}</div>
</div>
{% endfor %}
Any help here would be much appreciated.
Thanks in advance.
Note that I'm voting to close this as this question has been answered here.
Basically you need to use django's conditional expressions Case and When.
Here is a quick example of how this could look:
from django.db.models import Count, Case, When
# ...
Skill.objects.annotate(
num_porjects=Count(Case(
When(project__user_id=user.id, then=1),
output_field=CharField(),
))
)

Django: How to make a form with foreignkey

Hi I am working with Django and I am trying to make a little system to register people and teams.
So far I can create teams and people in the admin site.
Now, I want to make a public form, where i.e. a trainer can register his team.
The Team has a foreignkey to Bundesland (the state).
I want a dropdown list that shows the states, I already made in the admin site. And then chose from it in the form. My "python crash course"-book doesn't cover this, so please help me. The answers I found so far in the documentation and on stackoverflow didn't work for me.
models.py:
from django.db import models
class Bundesland(models.Model):
bdl_kurz = models.CharField(max_length=2) #abbreviation
bdl_lang = models.CharField(max_length=25) #full name
--snip--
class Team(models.Model):
bdl = models.ForeignKey(Bundesland)
name = models.CharField(max_length=40)
plz = models.CharField(max_length=5)
ort = models.CharField(max_length=40)
strasse = models.CharField(max_length=40)
strnr = models.CharField(max_length=5)
telefon = models.CharField(max_length=20)
email = models.EmailField()
--snip--
forms.py:
from django import forms
from .models import Team
class TeamForm(forms.ModelForm):
class Meta:
model = Team
bdl = forms.ModelChoiceField(queryset='bdl_lang.objects.all()), empty_label=None)
fields = ['name', 'plz', 'ort', 'strasse', 'strnr', 'telefon', 'email']
labels = {'plz': 'PLZ', 'ort': 'Ort', 'strasse': 'Straße', 'strnr': 'Hausnr.', 'telefon': 'Telefon', 'email': 'Email'}
new_team.html:
<p>New Team</p>
<form action="{% url 'teilnehmer:new_team' %}" method='post'>
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Submit data</button>
</form>
views.py: (if important)
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from .models import Bundesland, Gewichtsklasse, Team, Kaempfer
from .forms import TeamForm
--snip--
def new_team(request):
"""Add a new team."""
if request.method != 'POST':
# No data submitted; create a blank form
form = TeamForm()
else:
# POST data submitted; process data.
form = TeamForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('teilnehmer:index'))
context = {'form': form}
return render(request, 'teilnehmer/new_team.html', context)
So I can pick out a number of problems in your code.
bdl = forms.ModelChoiceField(queryset='bdl_lang.objects.all()), empty_label=None) - you do not need the starting quote right after the equal sign.
bdl_lang is a CharField so bdl_lang.objects.all() doesn't make sense. The queryset is an order dict of Django objects (defined via a model class). Replace this with bdl.objects.all(). From here, insert a __unicode__(self) method in your class Bundesland. This method should return the name you want in your choices. From looking at your code, it seems like you would want to return bdl_lang.
You need to include bdl in your fields option.
I hope this helps!

Form for multiple models

Suppose I have two models:
class Topic(models.Model):
title = models.CharField()
# other stuff
class Post(models.Model):
topic = models.ForeignKey(Topic)
body = models.TextField()
# other stuff
And I want to create a form contains two fields: Topic.title and Post.body. Of course, I can create the following form:
class TopicForm(Form):
title = forms.CharField()
body = forms.TextField()
# and so on
But I don't want to duplicate code, since I already have title and body in models. I'm looking for something like this:
class TopicForm(MagicForm):
class Meta:
models = (Topic, Post)
fields = {
Topic: ('title', ),
Post: ('body', )
}
# and so on
Also, I want to use it in class based views. I mean, I would like to write view as:
class TopicCreate(CreateView):
form_class = TopicForm
# ...
def form_valid(self, form):
# some things before creating objects
As suggested in comments, I could use two forms. But I don't see any simple way to use two forms in my TopicCreate view - I should reimplement all methods belongs to getting form(at least).
So, my question is:
Is there something already implemented in Django for my requirements? Or is there a better(simpler) way?
or
Do you know a simple way with using two forms in class based view? If so, tell me, it could solve my issue too.
You can create two separate forms having the required fields, for each model. Then show both forms in the template inside one html element. Both forms will get rendered and then submitted individually. You can then process the forms separately in the view.
class TopicForm(ModelForm):
class Meta:
model = Topic
fields = ("title", ..)
class PostForm(ModelForm):
class Meta:
model = Post
fields = ("body", ..)
In view:
form1 = TopicForm()
form2 = PostForm()
In template:
<form ...>
{{ form1 }}
{{ form2 }}
</form>
You can easily use form.save() and all other functions, without doing it all yourself.

Django - logic behind displaying relational tables in template

I have multiple related tables defined in my Django models:
# first models.py
from django.db import models
class Character(models.Model):
first_field = models.DateTimeField()
second_field = models.TextField()
# second models.py
from django.db import models
class Op(models.Model):
fk_character = models.ForeignKey('Character')
some_field = models.DateTimeField()
other_field = models.TextField()
class Participant(models.Model):
fk_op = models.ForeignKey('Op')
fk_character = models.ForeignKey('Character')
some_other_field = models.IntegerField(default=0)
For now, I'm sending this data from a view to template in a way like this:
# views.py
from django.shortcuts import render_to_response
from django.template import RequestContext
from second.models import MainModel
def home(request):
data = Op.objects.filter(some_field__isnull=True).order_by('-date')
rc = RequestContext(request, {'data':data})
return render_to_response('index.html', rc)
In this way I do have all the Op related data I need in my index.html template, but I'm struggling with logic to display this data in my template in a specific way. For example:
display a list of all Ops,
for each list item, check if Character is also a Participant in current Op item,
if it isn't, display some button, if it is than don't display the button
I know that template shouldn't handle any programming logic, but I'm also not sure what would be the best approach to solve this. Should I do all the logic in my view and construct a new object and send that object to my view or is there an easy way to solve this in template with current object I'm sending?
Update your model:
class Op(models.Model):
fk_character = models.ForeignKey('Character')
some_field = models.DateTimeField()
other_field = models.TextField()
def check_if_participant(self):
return bool(self.participant_set.all())
Display list of all Ops:
{% for op in data %}
{{op.some_field}}
{% if op.check_if_participant %}Yes - Character is participant {% endif %}
{% endfor %}

How can I specify form validation errors when they occur?

I'm quite new to Django Forms, and I'm facing a problem that I cannot solve. I've been googling and reading the docs, but I can't find the place where this is explained. My problem is that I have an Animal Model and a ModelForm:
class Animal(models.Model):
name = models.CharField(max_length=300)
age = models.PositiveSmallIntegerField()
race = models.ForeignKey(Race)
description = models.TextField()
state = models.ForeignKey(State)
pub_date = models.DateTimeField(auto_now_add=True)
adoption_limit = models.DateTimeField(blank=True, null=True)
location = models.ForeignKey(Location)
publisher = models.ForeignKey(User)
def __unicode__(self):
return self.name
class AnimalForm(ModelForm):
class Meta:
model = Animal
I render this info via urls.py, calling this view:
#login_required
def new_animal(request):
if request.method == "POST":
form = AnimalForm(request.POST)
if form.is_valid():
form.save()
return render_to_response('/')
else:
variables = RequestContext(request, {'e': form.errors})
return render_to_response('web/error.html', variables)
else:
form = AnimalForm()
variables = RequestContext(request, {'form': form})
return render_to_response('web/animal_form.html', variables)
It seems that I have an error introducing the adoption_limit field, so the data does not get saved in DB. This is because I just set a date and not a time into the text field displayed by the form.
I would like to know how can I do two things:
How can I send the error message to the form again, so that I can add a text next to the field that I have not set correctly? I.e., like the admin does.
How can I put the same input type for DateTimeField that I have in the admin interface? (with the Today and Now functions)
The way you have written your view, to display form errors, in your web/error.html template, simply output the errors:
{%if e %}
You had some errors in your submission!<br />
{{ e }}
{% endif %}
However, you don't have explicitly pass the errors list, it is part of the form itself. A bit of simplification:
variables = RequestContext(request, {'form': form})
return render_to_response('web/error.html', variables)
Then, in your template:
{% if form.errors %}
You have some errors!<br />
{{ form.errors }}
{% endif %}
For the second part of your question - to display the django date time widget - things get a bit more involved:
# First, you need to import the widget:
from django.contrib.admin.widgets import AdminSplitDateTime
from django.forms import TextField
# In your form class, you have to specify the widget
# for the field.
class AnimalForm(forms.ModelForm):
pub_date = models.TextField(widget=AdminSplitDateTime)
class Meta:
model = Animal
In order for this to work though, you have to make sure your admin media directory is accessible from your project (since all the javascript and css is included there). You'll also to have make sure that all the stylesheets are also added. It is much easier (and simpler) to use your own javascript form widget from your preferred library.
Finally, as stated in the documentation, if you override any fields, you need to add all the other validation logic yourself:
If you explicitly instantiate a form field like this, Django assumes
that you want to completely define its behavior; therefore, default
attributes (such as max_length or required) are not drawn from the
corresponding model. If you want to maintain the behavior specified in
the model, you must set the relevant arguments explicitly when
declaring the form field.
burhan's answer is spot on. Additionaly, You might probably want to hide the publisher on the form and deal with it in your view.
To do this, add exclude = ('publisher',) to class Meta in your ModelForm.
And then in your view:
if form.is_valid():
animal = form.save(commit=false)
animal.publisher = request.user
animal.save()
Otherwise, as it stands I think your form will show all users in a dropdown, which might not be what you want.

Categories