I'm trying to make a form, with the ModelForm class, for people to be recruited by a small company. So I need photo of their identity card (face and back) and their life card. The problem is that when I send the form, after selecting the photos from my computer, it does not register in the database (not even the path), and they do not a copy to the desired media folder. By cons, if I do it from the admin, it works, I can even open the image in my browser. However, they still do not upload to the media folder.
models.py :
from django.db import models
from django.contrib.auth.models import User
class UserExtention (models.Model):
user = models.OneToOneField(User, on_delete = models.CASCADE, null=True, verbose_name='utilisateur')
phone_number = models.CharField (max_length = 10, null = True, blank=True, verbose_name='numéro de téléphone')
postal_code = models.IntegerField (null = True, blank=True, verbose_name='code postal')
town = models.CharField (max_length=50, null=True, blank=True, verbose_name='ville')
address = models.CharField (max_length=500, null=True, blank=True, verbose_name='adresse')
id_card_recto = models.ImageField (upload_to = 'pictures/id_card_recto', null=True, blank=True, verbose_name="photo du recto de la carte d'identité")
id_card_verso = models.ImageField (upload_to = 'pictures/id_card_verso', null=True, blank=True, verbose_name="photo du verso de la carte d'identité")
vital_card = models.ImageField (upload_to = 'pictures/vital_card', null=True, blank=True, verbose_name="photo de la carte vitale")
hours_number = models.IntegerField (null=True, blank=True, verbose_name="nombre d'heure effectuée par le salarié")
def __str__(self):
return "Profil de {}".format(self.user.username)
forms.py :
from django import forms
from .models import UserExtention
from django.contrib.auth.models import User
class UserForm(forms.ModelForm):
class Meta:
model = User
fields = (
'password',
'username',
'first_name',
'last_name',
'email',
)
class UserExtentionForm(forms.ModelForm):
class Meta:
model = UserExtention
exclude = ('user', 'hours_number')
views.py :
from django.shortcuts import render
from .forms import UserForm, UserExtentionForm
def registration (request):
form = UserForm(request.POST or None, request.FILES)
form2 = UserExtentionForm(request.POST or None)
envoi = False
if form.is_valid() and form2.is_valid():
user = form.save()
user_extention = form2.save(commit = False)
user_extention.user = user
user_extention.save()
envoi = True
return render (request, 'registration/registration.html', locals())
The template :
<h1>Ceci est la page principale de l'application nommée "Registration"</h1>
{% if not envoi %}
<form action="{% url "registration" %}" enctype="multipart/form-data" method="post">
{% csrf_token %}
{{ form.as_p}}
{{ form2.as_p}}
<input type="submit" value="submit">
</form>
{% else %}
<p>Votre inscription a bien été prise en compte, vous pouvez à présent vous connecter dans l'onglet connexion</p>
{% endif %}
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static"),
)
MEDIA_ROOT = '/media/'
MEDIA_URL = '/media/'
Thanks for answer !
The form where you have media form fields is the UserExtentionForm, but in your code, you pass request.FILES only to the UserForm, that has no such media fields.
def registration (request):
if request.method == 'POST':
form = UserForm(request.POST, request.FILES)
form2 = UserExtentionForm(request.POST, request.FILES)
if form.is_valid() and form2.is_valid():
user = form.save()
user_extention = form2.save(commit = False)
user_extention.user = user
user_extention.save()
return redirect('some-view-name')
else:
form = UserForm()
form2 = UserExtentionForm()
return render (
request,
'registration/registration.html',
{'form': form, 'form2': form2}
)
Your view however contains some serious anti-patterns that are now (partially) migitated by the proposed solution:
you should not use request.POST or None, since a valid POST request can be empty, but it is still a POST request;
in case the POST request is successful, you should make a redirect, to implement the Post/Redirect/Get pattern [wiki]; and
please do not use locals(), since it will pass all variables to the template, even uninteded ones. Furthermore since it is not clear what you pass to the template, later you might be tempted to remove some variables from your view for optimization, and the IDE will not raise a warning/error that these variables are still necessary in the template.
If you want to add a message that the submission was successful, I advise you to use Django's messaging framework [Django-doc], and not pass a variable through the context. This framework is designed to post messages, regardless of the template itself, and prevent showing the same message multiple times.
Related
I need to create a form of persons, where the camp Name must receive an API response. I created the formulary e rendered the api response in template, but I can´t put it in my formulary, in order to save in my Models camp Name. So, I just want to save my API respone inside my forms and in my database.
Views
def cadastro(request):
url = 'https://gerador-nomes.herokuapp.com/nome/aleatorio'
api = requests.get(url)
nome_api = ' '.join(api.json())
form = PessoaForm()
form.nome = api
if request.method == 'POST':
form = PessoaForm(request.POST)
if form.is_valid():
form.cleaned_data('nome')
form.save()
return redirect('/')
context = {'form': form, 'api': nome_api}
return render(request, 'base/pessoa_form.html', context)
pessoa_form.html
<body>
<form action="" method="post">
{% csrf_token %}
{{form}}
<input type="submit" name="Cadastrar">
</form>
</body>
</html>
Forms
from django.forms import ModelForm
from . models import Pessoa
class PessoaForm(ModelForm):
class Meta:
model = Pessoa
fields = '__all__'
Models
from django.db import models
class Pessoa(models.Model):
name= models.CharField(max_length=255, null=True)
lastname= models.CharField(max_length=255, null=True)
age= models.IntegerField(null=True)
birthday_date= models.DateField()
email = models.CharField(max_length=255, null=True)
nickname= models.CharField(max_length=255, null=True, blank=True)
note = models.CharField(max_length=500, null=True, blank=True)
def __str__(self):
return self.nome
class Meta:
ordering = ['nome', 'sobrenome']
I have tried some things of my head but nothing actually worked, like try to access the variable Name in my forms inside my template and inside my views.
[English]
Well, by judging by the variable names you've chosen, I guess you're brazilian, so I'll post the answer in portuguese as well.
I don't know if this is the best practice, but you could just append the api response as a new field in the request.POST, for instance:
[Portuguese Translation]
Pelos nomes que tu usou, imagino que sejas BR então vou botar a resposta em Português também além do inglês.
Não sei se é o método que segue as melhores práticas, mas você poderia simplesmente adicionar a resposta da API como um campo novo no teu request.POST, por exemplo:
def cadastro(request):
url = 'https://gerador-nomes.herokuapp.com/nome/aleatorio'
api = requests.get(url)
nome_api = ' '.join(api.json())
form = PessoaForm()
if request.method == 'POST':
updated_request = request.POST.copy()
updated_request.update({'name': nome_api})
form = PessoaForm(updated_request)
if form.is_valid():
form.save()
return redirect('/')
context = {'form': form, 'api': nome_api}
return render(request, 'base/pessoa_form.html', context)
I have pre-filled forms for my user (the django user + my profile one with more informations) that just doesn't want to be saved. No error message, no redirection, I press the button "edit", and then... same page, with the modifications already written. But the database isn't changed.
So, here is the code :
forms.py :
class UpdateUser(forms.ModelForm):
email = forms.EmailField(required=True)
class Meta:
model = User
fields = ('email', 'username')
def clean_email(self):
email = self.cleaned_data.get('email')
username = self.cleaned_data.get('username')
if email and User.objects.filter(email=email).exclude(username=username).count():
raise forms.ValidationError('Cette adresse email est déjà utilisée, veuillez en indiquer une autre')
return email
class UpdateProfil(forms.ModelForm):
class Meta:
model = Profil
fields = ("society", "thingA", "thingB", "thingC", "thingD", "thingE")
views.py :
#login_required
def edit_profile(request):
user = User.objects.get(username = request.user)
profile = Profil.objects.get(user = user)
if request.method == 'POST':
form1 = UpdateUser(request.POST, instance=user)
form2 = UpdateProfil(request.POST, instance=profile)
if form1.is_valid() and form2.is_valid():
form1.save()
form2.save()
return HttpResponseRedirect('Register/view_profile.html')
else:
form1 = UpdateUser(initial={"email": user.email,
"username": user.username})
form2 = UpdateProfil(initial={"society": profile.society,
"thingA": profile.thingA,
"thingB": profile.thingB,
"thingC": profile.thingC,
"thingD": profile.thingD,
"thingE": profile.thingE})
return render(request, 'Register/edit_profile.html', locals())
and the template:
<form action="" method="post">
{% csrf_token %}
<p>Email : </p>{{ form1.email }}
<p>Nom d'utilisateur : </p>{{ form1.username }}
<p>Société (entreprise, association, établissement...") :</p>
{{ form2.society }}
<p>Si vous êtes membres d'un ou plusieurs centres pilotes, veuillez les cocher ci-dessous :</p>
<p>thingA : {{form2.thingA }}</p>
<p>thingB : {{form2.thingB }}</p>
<p>thingC : {{form2.thingC }}</p>
<p>thingD : {{form2.thingD }}</p>
<p>thingE : {{form2.thingE }}</p>
<button type="submit">Editer</button>
</form>
models.py
class Profil(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
deadline = models.DateField(blank=True, null=True,
default=None,
verbose_name="Date limite de validité")
thingA = models.BooleanField(default=False)
thingB = models.BooleanField(default=False)
thingC = models.BooleanField(default=False)
thingD = models.BooleanField(default=False)
thingE = models.BooleanField(default=False)
society = models.CharField(max_length = 255,
blank=True, null=True,
verbose_name="Société",
help_text="Le nom de votre entreprise, association, établissement...")
def __str__(self):
return "Profil de {0}".format(self.user.username)
After trying different thing and reading everything I could on the topic without finding a solution working, I try to think outside the box : I thought that maybe it was because I didn't put the password inside the form, but it should'nt play a role here, right ?
So, maybe a permissions problem ? I try giving permisions, for both the model. Still not working...
Thanks for your time, and sorry for my English mistakes...
So, I tinkered with my code a bit, and it's now working, I don't know why, and if someone know, I'm all ears!
I'll put it here, in case it could be helpful for others.
so, new view's code :
#login_required
def edit_profile(request):
user = User.objects.get(username = request.user)
profile = Profil.objects.get(user = user)
if request.method == 'POST':
form1 = UpdateUser(request.POST, instance=user)
form2 = UpdateProfil(request.POST, instance=profile)
if form1.is_valid() and form2.is_valid():
form1.save()
form2.save()
return redirect("viewProfile") # Here is the only change I finished with
else:
form1 = UpdateUser(initial={"email": user.email,
"username": user.username})
form2 = UpdateProfil(initial={"society": profile.society,
"thingA": profile.thingA,
"thingB": profile.thingB,
"thingC": profile.thingC,
"thingD": profile.thingD,
"thingE": profile.thingE})
return render(request, 'Register/edit_profile.html', locals())
So, I guess the problem was with the httpResponseRedirect, don't know why, since there was no error, and nothing was saved, but well... I won't look a gift horse in the mouth! It's working now. 2020 suddenly seems a lot better! Until tomorrow that is =P
Thanks to those who tried to help me!
Problem description: UserProfile form doesn't save any data.
I am creating a new User and automatically create a UserProfile object for him (so I'm extending UserProfile), so I can go to admin page and fill all the fields . But when I'm trying to do it from client side, my form just doesn't catch the data.
Also the strangest moment is that I can change username and email using UserChangeForm, so I'm trying to do the same for UserProfileObject.
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User)
image = models.ImageField(upload_to='profile_image', blank=True)
title = models.CharField(max_length=100, default = '')
first_name = models.CharField(max_length=200, default = '')
last_name = models.CharField(max_length=200, default = '')
subject = models.ManyToManyField('Subject', related_name='tutor_type', default = '', help_text="Select a subject")
AREA_STATUS = (
('Jerusalem', 'Jerusalem'),
('Tel Aviv', 'Tel Aviv'),
('Haifa', 'Haifa'),
('Eilat', 'Eilat')
)
area = models.CharField(max_length=200, choices=AREA_STATUS, blank=True, default='', help_text='Tutor area')
# Foreign Key used because tutor can only have one area, but area can have multiple tutors
# Author as a string rather than object because it hasn't been declared yet in file.
description = models.TextField(max_length=4000, help_text="Enter a brief description about yourself")
charge = models.IntegerField(default = '0')
# ManyToManyField used because Subject can contain many tutors. Tutors can cover many subjects.
# Subject declared as an object because it has already been defined.
LANGUAGE_CHOICES = (
('English','English'),
('Hebrew','Hebrew'),
('Russian','Russian'),
('French','French'),
('Arabic','Arabic'),
)
language = models.CharField('Language', choices = LANGUAGE_CHOICES, max_length=50, null=True)
def __str__(self):
return self.user.username
def display_subject(self):
"""
Creates a string for the subject. This is required to display subject in Admin.
"""
return ', '.join([ subject.name for subject in self.subject.all()[:3] ])
display_subject.short_description = 'Subject'
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender = User)
forms.py::
class EditProfileForm(UserChangeForm):
class Meta:
model = User
fields = (
'username',
'email',
'password'
)
class EditExtendedProfileForm(UserChangeForm):
class Meta:
model = UserProfile
fields = '__all__'
exclude = ('user',)
views.py:
def edit_profile(request):
if request.method == 'POST':
form = EditProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect(reverse('accounts:view_profile'))
else:
form = EditProfileForm(instance=request.user)
args = {'form': form}
return render(request, 'accounts/edit_profile.html', args)
def edit_extended_profile(request):
if request.method == "POST":
form = EditExtendedProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
return redirect(reverse('accounts:view_profile'))
else:
return redirect(reverse('accounts:edit_extended_profile'))
else:
form = EditExtendedProfileForm(instance = request.user)
args = {'form':form}
return render(request, 'accounts/edit_extended_profile.html', args)
edit_extended_profile.html:
{% extends "base.html" %}
{% block head %}
<title>Profile</title>
{% endblock %}
{% block body %}
<div class = "container">
<form method="POST">
{% csrf_token %}
{{form.as_p}}
<button type = "submit" class = "btn btn-success">Submit</button>
</form>
</div>
{% endblock %}
and it is the same template as for edit_profile view.
No traceback, no errors. Any help will be appreciated. Thanks in advance.
I've been trying to add some user uploaded profile picture to my website. It works fine when I do it from the admin, the image is showed and all the engines seems to be working fine (image going to the correct upload location and so on). The problem is when I try to do the same thing from my view.
I noticed that the print("upload_location") only appears when I do it from the admin. The weird thing is that all the other fields in my Profile model are working fine (like name "foo" is updated to "foobar") and not only in the admin, but in the view as well. The issue is only with the ImageField.
I believe it could have something to do with the way I'm handling the form.is_valid(), but I've been playing around with that and nothing changed (I know it is working to some extend, since HttpResponseRedirect is working.
Any ideas?
views.py
...
#login_required
def profile_update(request, username=None):
obj = get_object_or_404(User, username=username)
user = obj.profile
form = ProfileForm(request.POST or None, instance = user)
context = {
"form": form
}
if form.is_valid():
form.save()
return HttpResponseRedirect('/profiles/{username}'.format(username=user.user))
template = 'profile_update.html'
return render(request, template, context)
forms.py
from django import forms
from .models import Profile
class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = [
"profilePic",
"nome",
...
]
def profile(self, request, user):
print('printing forms')
user.uf = self.cleaned_data['uf']
user.cidade = self.cleaned_data['cidade']
user.telefone = self.cleaned_data['telefone']
user.save()
models.py
...
User = settings.AUTH_USER_MODEL # 'auth.User'
def upload_location(instance, filename):
print("upload_location")
return "%s/%s" %(instance.user, filename)
class Profile(models.Model):
user = models.OneToOneField(User)
id = models.AutoField(primary_key=True)
width = models.IntegerField(default=0, null=True, blank=True,)
height = models.IntegerField(default=0, null=True, blank=True,)
profilePic = models.ImageField(
upload_to = upload_location,
blank=True, null=True,
verbose_name = 'Foto de Perfil',
width_field="width",
height_field="height",
)
...
template.html
...
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
{{ form|crispy }}
<input type="submit" value="Enviar" class="btn btn-primary"/>
</form>
...
You need to add FILES into the form.
form = ProfileForm(request.POST or None, request.FILES or None, instance = user)
Docs: https://docs.djangoproject.com/en/1.10/topics/http/file-uploads/
Altough my form to send an offer is correctly displayed and I have no error when submitting the offers, they are not saved in my database.
I know that by checking the admin site, no objects are saved.
But on the other hand, I already have written the code for the registration and users are saved in the database.
I suspect the ForeignKey relation between my 2 models as the culprit.
models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=30, unique=True, blank=False)
password1 = models.CharField(max_length=40, blank=False)
password2 = models.CharField(max_length=40, blank=False)
mail = models.EmailField(unique=True, blank=False)
birthday = models.DateField(blank=False)
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date d'inscription")
def __str__(self):
return self.username
class Offer(models.Model):
publisher = models.ForeignKey(User)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")
def __str__(self):
return self.publisher.username
forms.py
from django import forms
from django.contrib import admin
from django.contrib.auth.hashers import make_password, check_password
from django.utils.translation import ugettext_lazy as _
from myuser.models import User, Offer
class UserCreationForm(forms.ModelForm):
class Meta:
model = User
widgets = {
'password1' : forms.PasswordInput(),
'password2' : forms.PasswordInput(),
}
fields = ("username", "password1", "password2", "mail")
def clean_password2(self):
# Check that the two password entries match
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("les mots de passes ne correspondent pas")
return password2
def save(self, commit=True):
# Save the provided password in hashed format
user = super(UserCreationForm, self).save(commit=False)
user.password1 = make_password(self.cleaned_data["password1"])
user.password2 = make_password(self.cleaned_data["password2"])
if commit:
user.save()
return user
class LoginForm(forms.Form):
username = forms.CharField(label="nom d'utilisateur")
password = forms.CharField(label="mot de passe",
widget = forms.PasswordInput)
def clean(self):
cleaned_data = super(LoginForm, self).clean()
username = cleaned_data.get('username')
password = cleaned_data.get('password')
user = User.objects.get(username=username)
if check_password(password, user.password1):
return cleaned_data
else:
raise forms.ValidationError("Le nom d'utilisateur et le mot de passe ne correspondent pas")
class SendOfferForm(forms.ModelForm):
class Meta:
model = Offer
fields = ('content',)
Views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect, HttpResponse
from django.core.exceptions import ObjectDoesNotExist
from myuser.models import User, Offer
from myuser.forms import UserCreationForm, LoginForm, SendOfferForm
def get_logged_user_from_request(request):
if 'logged_user_id' in request.session:
logged_user_id = request.session['logged_user_id']
return User.objects.get(id=logged_user_id)
else:
return None
def register(request):
registered = False
if request.method == 'POST':
user_form = UserCreationForm(data=request.POST)
if user_form.is_valid():
user = user_form.save()
registered = True
else:
print(user_form.errors)
else:
user_form = UserCreationForm()
return render(request,
'myuser/create_account.html',
{'user_form': user_form, 'registered': registered} )
def login(request):
if request.method=='POST':
form = LoginForm(request.POST)
try:
if form.is_valid():
user = User.objects.get(username=request.POST.get('username'))
logged_user = User.objects.get(username=request.POST.get('username'))
request.session['logged_user_id'] = logged_user.id
return HttpResponseRedirect('/')
else:
error = "le nom d'utilisateur et le mot de passe ne correspondent pas"
return HttpResponse("Invalid login details supplied.")
except User.DoesNotExist:
return HttpResponse("Invalid login details supplied.")
else:
form = LoginForm
return render(request, 'myuser/mylogin.html', locals())
def send_offer(request):
sent = False
logged_user = get_logged_user_from_request(request)
if logged_user:
if request.method == 'POST':
try:
offerform = SendOfferForm(request.POST, instance=logged_user)
if offerform.is_valid():
sent = True
offerform.save()
else:
print(offerform.errors)
except:
return HttpResponse("drapeau except")
else:
offerform = SendOfferForm(instance=logged_user)
else:
return HttpResponse("Vous n'êtes pas connecté")
return render(request, 'myuser/send_offer.html', locals())
urls.py
from django.conf.urls import patterns, url, include
from django.views.generic import TemplateView
urlpatterns = patterns('myuser.views',
url(r'^inscription/$', 'register', name='create_account'),
url(r'^connexion/$', 'login', name='login'),
url(r'^envoyer_une_offre$', 'send_offer', name='send_offer'),
)
send_offer.html
{% extends "base.html" %}
{% block content %}
<h1> Offer </h1>
{% if not sent %}
<p> write your offer <p/>
<form action="{% url "send_offer" %}" method='POST' class='sendofferform'>
{{ form.errors }}
{{ form.non_field_errors }}
{% csrf_token %}
{{ offerform.as_p }}
<input type="submit" value="Submit" />
</form>
{% else %}
Offer is published
publish another offer?<br />
get back to the homepage<br />
{% endif %}
{% endblock %}
admin.py
from django.contrib import admin
from myuser.models import User, Offer#, Message
class UserAdmin(admin.ModelAdmin):
list_display = ('id', 'username', 'status', 'college', 'apercu_description')
list_filter = ('id', 'username', 'birthday')
date_hierarchy = 'date'
ordering = ('date', )
search_fields = ('username', 'description')
def apercu_description(self, User):
text = User.description[0:40]
if len(User.description) > 40:
return '%s' % text
else:
return text
class OfferAdmin(admin.ModelAdmin):
list_display = ('id', 'publisher', 'apercu_offre')
list_filter = ('id', )
date_hierarchy = 'date'
ordering = ('date', )
search_fields = ('publisher',)
def apercu_offre(self, Offer):
text = Offer.content[0:40]
if len(Offer.content) > 40:
return '%s' % text
else:
return text
admin.site.register(User, UserAdmin)
admin.site.register(Offer, OfferAdmin)
All of the other function works (register and login) and the register() function correctly saves the user but the send_offer() function, which is very similar to the register function doesn't work and after searching on internet for hours, I still have no idea why the offers are not saved.
But when I try to add an offer in the admin site, it seems to work.
Besides, I tried to save an offer in the python manage.py shell :
>>> Offer.publisher = "a"
>>> Offer.content = "lalalala"
>>> Offer.save()
Traceback (most recent call last):
File "/usr/local/lib/python3.4/dist-packages/django/core/management/commands/shell.py", line 69, in handle
self.run_shell(shell=options['interface'])
File "/usr/local/lib/python3.4/dist-packages/django/core/management/commands/shell.py", line 61, in run_shell
raise ImportError
ImportError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3.4/code.py", line 90, in runcode
exec(code, self.locals)
File "<console>", line 1, in <module>
TypeError: save() missing 1 required positional argument: 'self'
EDIT : The solution was to add these lines :
if offerform.is_valid():
sent = True
offer = offerform.save(commit=False)
offer.publisher = User.objects.get(id=logged_user.id)
offer.save()
Please, note that logged_user is a function, which is described in view.py.
On a ModelForm if you don't pass any object when its instantiated It will create a new object in the database of the specified type. So in this case on your SendOfferForm you have specified Offer. IF you do pass an existing object it has to be the specified type in this case Offer.
It looks like you are trying to automatically fill the publisher field with the currently logged in user. In order to do this you need to manually set the publisher field to an instance of User.
If you intend for the form to create a new Offer in the database do this
offerform = SendOfferForm(request.POST)
If you are trying to update a current offer THEN you would need to pass an instance object into the form
offerform = SendOfferForm(request.POST, instance=someOffer)
To manually add the user field call save on your form with commit=False this will return an offer object WITHOUT saving to the DB. At this point you have a chance to customize the data in object and then save.
offer = offerform.save(commit=False)
offer.publisher = request.user.id
offer.save()
All of this is explained in more detail in the django documentation
ModelForm documentation