Django: How to make a form with foreignkey - python

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!

Related

Django Inclusion Tag doesn't post to database

I'm trying to build a form to save Names and Email Adresses to my database. However, it doesn't save...
I've used an Inclusion Tag because I want to use the same form in different templates.
This is my models.py:
class Contact(models.Model):
FRAU = 'FR'
HERR= 'HR'
GENDER_CHOICES = (
(FRAU, 'Frau'),
(HERR, 'Herr'),
)
gender = models.CharField(max_length=2, choices=GENDER_CHOICES, default=FRAU)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=200)
email = models.EmailField()
def __unicode__(self):
return "%s %s" %(self.first_name, self.last_name)
This is my forms.py:
class FragenContactForm(ModelForm):
class Meta:
model = Contact
fields = ['gender', 'first_name', 'last_name', 'email']
This is my custom tags module:
from django import template
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from fragen.forms import FragenContactForm
register = template.Library()
#register.inclusion_tag('fragen/askforoffer.html', takes_context=True)
def askforoffer(context):
form = FragenContactForm(context['request'].POST or None)
if context['request'].method=='POST':
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('fragen/thanks.html'))
else:
messages.error(context['request'], "Error")
return {'form': FragenContactForm()}
After I fill in and submit the form, I see nothing in my database. Am I missing something?
Thanks!
I've used an Inclusion Tag because I want to use the same form in
different templates.
You can simply reuse the form - or as your form in this case is very simple, you can use the CreateView generic class based view and reduce your code even further.
Your view would contain just the following:
class OfferForm(CreateView):
template_name = 'fragen/askforoffer.html'
model = Contact
fields = ['gender', 'first_name', 'last_name', 'email']
success_url = 'fragen/thanks.html'
Django will automatically create the ModelForm, and handle the error redirection and saving of the fields for you.
In your fragen/askforoffer.html template, you need just this:
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
Finally, in your urls.py:
url(r'^submit-offer/$', OfferForm.as_view(), name='offer-form')
To display the same form in multiple places, just map it to a URL:
url(r'^another-form/$', OfferForm.as_view(), name='another-form')
Finally, __unicode__ method should return a unicode object; so in your model:
def __unicode__(self):
return u"{} {}".format(self.first_name, self.last_name)
The way you are trying to do it will not work because the template tag code will be executed before the template is rendered; so by the time the user sees the form, your tag code is already finished. There is no way to "trigger" it again; which is why you need a traditional view method which will accept the data entered into the form.
Post is a method of server request which is handled by views.
Inclusion tag is rendered along with the page (that is during server response). Thus page context can not get request.POST - of cause, if you don't send POST deliberately as a context variable to the page (but it won't be request.POST - just some_variable). It looks a bit weird..
You have to handle form-processing in a view function.
from django.shortcuts import redirect, render
from fragen.forms import FragenContactForm
def askforoffer(request):
form = FragenContactForm(request.POST or None)
if form.is_valid():
form.save()
return redirect('specify_thank_url_here')
return render(request, 'fragen/askforoffer.html',
{ 'form': form })
I've never seen any form processing in an inclusion tag and I doubt this will work. Above view-function may point you in the right direction.

Form, Create and Update View for Many-To-Many Relationship

This is my first question here and I am writing because I'm going mad with this, even after reading documentation and a lot of answers here. Thank you very much and sorry for my bad english!
I have these models:
class Profile(models.Model):
name = models.CharField(max_length = 255, blank = False)
user = models.ForeignKey(User, blank = True, null = True)
class Category(models.Model):
name = models.CharField(max_length = 50, blank = False)
class ProfileCategory(models.Model):
profile = models.ForeignKey(Profile)
category = models.ForeignKey(Category)
class Meta:
unique_together = ('profile', 'category')
Is this model correct?
I suppose to have already the database with the categories saved. I need a page where the user can create a new profile and choose the categories from a list of checkbox. Should I use two form in the same page, one for the profile and one for choose the categories or a single form? I think i need a ModelMultipleChoiceField for the categories.
I also need a view that display the same form already filled with the profile and the categories where the user can change the profile name, and add or remove categories.
Tell me if you need more information and thank you very much.
ProfileCategory model is unnecessary here. Use ManyToMany field to achieve the same result:
class Category(models.Model):
name = models.CharField(max_length=50, blank=False)
class Profile(models.Model):
name = models.CharField(max_length=255, blank=False)
user = models.ForeignKey(User, blank=True, null=True)
categories = models.ManyToManyField(Category, blank=True)
Now you can edit profile with a single form as any other model. The only thing you should to remember is to call save_m2m() in case if you override the form's save() method.
app/forms.py
from django import forms
from app.models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
widgets = {
'categories': forms.CheckboxSelectMultiple,
}
app/views.py
from django.views.generic.edit import CreateView
from app.forms import ProfileForm
from app.models import Profile
class ProfileCreate(CreateView):
form_class = ProfileForm
model = Profile
templates/app/profile_form.html
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Create" />
</form>
app/urls.py
from app.views import ProfileCreate
urlpatterns = patterns('',
...
url(r'^profile/create/', ProfileCreate.as_view()),
)
To update profiles use UpdateView with the same ProfileForm class and template.
EDIT: If you need additional fields in the ProfileCategory model then you can set it as intermediary model with through argument of the ManyToManyField. To edit such models you have to use formsets. Read more about it here, here and here.

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 %}

Django form with list of foreign key fields

I have the below models for which I'm trying to create a form for:
class Letter(models.Model):
title = models.CharField(max_length=100)
publish_date = models.TimeField()
class LetterRecipients(models.Model):
letter = models.ForeignKey(Letter)
recipient_name = models.CharField(max_length=200)
recipient_rating = models.IntegerField()
has_responded = models.BooleanField()
I'd like a single form that allows the user to enter a title and publish_date for the letter, and in the same form enter multiple recipients by name and rating.
Can anyone help with creating the form model for this? I can't figure out how to have django generate this form using {{ form.as_p }}. I think I'll have to use jQuery to create the additional recipient rows on the HTML page, but how would I get django to parse those into the model?
Any help would be greatly appreciated.
Thanks.
Ark
Ark, you can use ModelMultipleChoiceField in Django form. Here are some roughly example. I create "posts" apps just for quick testing :
forms.py
from django import forms
from django_test.posts.models import Letter, LetterRecipients
class LetterForm(forms.Form):
title = forms.CharField()
publish_date = forms.TimeField()
recepient = forms.ModelMultipleChoiceField(
queryset=LetterRecipients.objects.all()
)
models.py
from django.db import models
class Letter(models.Model):
title = models.CharField(max_length=100)
publish_date = models.TimeField()
class LetterRecipients(models.Model):
letter = models.ForeignKey(Letter)
recipient_name = models.CharField(max_length=200)
recipient_rating = models.IntegerField()
has_responded = models.BooleanField()
def __unicode__(self):
return self.recipient_name
views.py
# Create your views here.
from django_test.posts.forms import LetterForm
from django.shortcuts import render
def index(request):
form = LetterForm()
data = {'form': form}
return render(request, 'posts/index.html', data)
index.html
{% load url from future %}
{{ form.as_p }}

Pre-populate form, from multiple models, for the purpose of "update"

I'm rather new to Django, so please forgive the rather newbish question I need to ask. Basically, I have a problem I'm having great difficulty implementing after countless searches.
What I would like to do, is create an input form in a page, and populate it with data from the database. I would then like to be able to click on a button to update whatever changes I make to the form. The catch here is that the form will take data from two models, a main model, and a submodel related to it. Exactly like how an Invoice might have multiple items within it.
The furthest I've gotten was displaying the main model but not the submodel, or displaying everything, but the button not doing anything. Here are the code:
models.py
import datetime
from django.db import models
from django.utils import timezone
class InvoiceList(models.Model):
invoice_number = models.IntegerField(default=0)
recipient = models.CharField(max_length=100)
issue_date = models.DateField()
deadline = models.DateField()
additional_info = models.CharField(max_length=500)
def __str__(self):
return self.recipient
class InvoiceItem(models.Model):
item_description = models.CharField(max_length=150)
number = models.IntegerField(default=1)
price = models.IntegerField(default=0)
tax = models.IntegerField(default=0)
list = models.ForeignKey(InvoiceList)
def __str__(self):
return self.item_description + " (" + str(self.list) + ")"
forms.py (EDITED) (added "can_delete+=False" to prevent appearance of "Delete" checkbox. Checkbox needs to be checked in order for "is_valid" to flag as True... which is nonsense. So remove that!)
import datetime
from bill.models import *
from django.utils import timezone
from django.forms import ModelForm
from django.forms.models import inlineformset_factory
class InvoiceListForm(ModelForm):
class Meta:
model = InvoiceList
fields = ['invoice_number', 'recipient', 'issue_date', 'deadline', 'additional_info']
class InvoiceItemForm(ModelForm):
class Meta:
model = InvoiceItem
exclude = ('list',)
fields = ['item_description', 'number', 'price', 'tax']
InvoiceFormSet = inlineformset_factory(InvoiceList, InvoiceItem, fields=('id','item_description','number','price','tax',), extra=0, can_delete=False)
Partial view of views.py (EDITED) (Probably can be cleaned heavily, but here's the engine to make it update!)
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404, render
from django.utils import timezone
from django.core.context_processors import csrf
from django.forms.formsets import formset_factory, BaseFormSet
from django.forms.models import modelformset_factory
from bill.forms import *
from .forms import InvoiceFormSet
from django.views.generic.edit import UpdateView
from django.forms import inlineformset_factory
from django.template import RequestContext
def update_edit(request, invoice_id):
a = get_object_or_404(InvoiceList, pk=invoice_id)
b = InvoiceFormSet(instance=a, prefix="item")
c = InvoiceListForm(instance=a, prefix="list")
if request.method == 'POST':
gaga = InvoiceListForm(request.POST, instance=a, prefix="list")
lala = InvoiceFormSet(request.POST, instance=a, prefix="items")
if gaga.is_valid() and lala.is_valid():
gaga.save()
lala.save()
return HttpResponse('Good, saved the thing!')
else:
return HttpResponse('No shway! Didn't save cos not valid!')
else:
c = InvoiceListForm(instance=a, prefix="list")
b = InvoiceFormSet(instance=a, prefix="items")
d = {'invoice_info': c, 'items': b}
d.update(csrf(request))
return render(request, 'bill/update.html', {'form': invoice_info, 'items': invoice_stuff})
Here's the frontend update.html (EDITED) (Added management thing)
<h1>Updating Invoice</h1>
<form action="." name="stock_details" method="post">
{% csrf_token %}
<hr>
{{ form.as_p }}
{{ items.management_form }}
{% for item in items %}
{{ item }}
{% endfor %}
<hr>
<input type='submit' value='Update'/>
</form>
I would put in {{ item.as_p }}, but that doesn't display anything unfortunately. I'm not sure why. The above displays all of the data. The exception are the items, they don't appear inside an input tag... I'll have to do that manually I suppose.
EDIT - I've modified the code to show both the invoice and its child items into the formset, which displays well and has content populated. I'm trying to figure out how to save the form at this point, assuming there are changes. Help?
All solved! Thank you very much!

Categories