How can I specify form validation errors when they occur? - python

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.

Related

Django: How to retrieve the logged in user's details

I am in the process of learning Django. I am trying to create a simple directory web app. I am able to print out all the users details for the main directory page. However, I want to add a feature that when a person logs into the directory app they are brought to their 'profile' page where they will be able to see all their own details e.g. business name, profile pic.
I know how to retrieve the default fields e.g. username and email. But cannot seem to retrieve the custom fields that I declared myself. Here is my attempts so far...
Models.py:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserProfileInfo(models.Model):
user = models.OneToOneField(User,on_delete=models.DO_NOTHING)
#additional classes
business_name = models.CharField(max_length=191,blank=True)
trade = models.CharField(max_length=191,blank=True)
portfolio_site = models.URLField(blank=True)
profile_pic = models.ImageField(upload_to='profile_pics',blank=True)
def __str__(self):
return self.user.username
Views.py:
#login_required
def profile(request):
profile = UserProfileInfo.objects.filter(user=request.user)
context = { 'profile': profile }
return render(request, 'dir_app/profile.html',context)
Profile.html:
<div class="container-fluid text-center">
{% for p in profile %}
<h3>{{p.business_name}}</h3>
{% endfor %}
</div>
Since UserProfileInfo is related to User via OneToOneField, you can have one UserProfileInfo per User. So, instead of Filter, you can simply get your desired UserProfileInfo object through your current (logged in) User as follows.
views.py,
profile = UserProfileInfo.objects.get(user=request.user)
Also, before you can get a request.user object, you have to make sure that your user is authenticated and logged in. Otherwise, you might get None in place of a User object and therefore, no associated UserProfileInfo.
Since it is a OneToOneField there is only one Profile object for a User, you thus can obtain this with:
#login_required
def profile(request):
profile = request.user.userprofileinfo
return render(request, 'my_template.html',{'profile': profile})
Then in the template, you render it with:
{{ profile.business_name }}
you can use it directly on template without sending it f:
{{request.user.userprofile}}

django 2 - User and userprofile models, how to obtain all fields in a single query?

Update and solution below.
I've been looking for a solution but I'm not finding anything that sticks out.
I've created a Profile model which is linked to the standard User model via one-to-one field which is working in admin. I want to pull all fields/data for both models into a single queryset. I'm trying to create a user editing form and I want to pull in all fields for User and Profile based on the current logged in user and display those fields which I will have a page to edit and save those fields.
What are the best options to achieve this, simple is better.
class Profile(models.Model):
address = models.blablabla
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
def profile_edit(request):
form = UserProfileForm(request.POST or None)
instance = Profile.objects.all().filter(user__username=request.user).values # This is the place I need to create a single queryset.
if request.method == "POST" and form.is_valid():
form = UserProfileForm(request.POST, instance=instance)
user_form = form.save()
print("POST event")
else:
form = UserProfileForm(instance=instance)
print(form)
return render(request, 'frontend/profile_edit.html', {'form': form})
I'm manually creating the forms in the template so I would like to have something like {{ form.username }} {{ form.profile.address }} or something like that. I'm likely doing things poorly, I'm new to django.
UPDATE
Complete solution
Complete steps to gain access to user and profile models in code and template.
I decided not to replace the User model with my own in case I missed out on features provided by django. It also seemed to complicate things that might hurt later on. So I've gone with the separate UserProfile model and attached it to the User model. Here is what I did for future readers.
models.py
from django.db.models.signals import post_save
class UserProfile(models.Model):
#take note of the related_name='profile' this is used to reference the fields in code and template.
#Each field of type 'text' I added default='' at the end, I got an error when it was int based so I removed the flag for that field. I read this might cause an error when you try and auto-create the profile, see what works for you and you might not want it.
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
country = models.CharField(max_length=2, blank=True, null=True, default='')
...
# Auto-create the profile upon user account creation. It's important to start with a fresh database so the user and profile ID's match.
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
# Create your models here.
#In code, you can access your user and profile field data like so.
request.user.profile.fieldname
request.user.fieldname
In template you can do the same
{{ user.fieldname }}
{{ user.profile.fieldname }}
There's no need for a query here at all - you don't want a queryset, you want a single instance.
In this case, request.user.profile will give you the entire Profile object related to the current user.

Django form to display user attributes

I'm having an odd problem that I can't remedy: Django is not displaying any of the user's attributes except for the username when using a form that includes a foreign key attribute between two models.
The associated username is showing up correctly, but none of the other attributes are (first name, last name, email, etc).
Similarly, the debugging print statement that I've placed in the views.py is correctly printing the user's attributes in the terminal output.
Why aren't any of the user's attributes showing up in the html template?
models.py
class UnitGroup(models.Model):
unit_name = models.CharField(max_length=150, verbose_name='Unit Name')
class UnitUser(models.Model):
user = models.OneToOneField(User)
unit = models.ForeignKey(UnitGroup)
ROLES = ((0, 'Admin'), (1, 'Member'))
role = models.IntegerField(null=True, blank=True, verbose_name='Role', choices=ROLES)
def __string__(self):
return self.user.first_name
forms.py
class UserGroupForm(forms.ModelForm):
class Meta:
model = UnitUser
fields = '__all__'
views.py
from units.forms import UnitForm, UnitDetailsForm, UserGroupForm
from units.models import UnitGroup, UnitDetails, UnitUser
from django.contrib.auth.models import User
def edit_members(request):
if request.user.is_authenticated() \
and request.method == 'GET' \
and 'unit' in request.GET:
unit_group = request.GET['unit']
unit_users = UnitUser.objects.filter(unit=unit_group)
unit_forms = []
for i in unit_users:
# debug
print(i.user.first_name)
print(i.user.last_name)
unit_forms.append(UserGroupForm(instance=i))
return render(request, 'units/edit-members.html', {'unit_forms': unit_forms})
edit-members.html
{% for form in unit_forms %}
user: {{ form.user }} <br>
first name: {{ form.user.first_name }}
{% endfor %}
Your UserGroupForm is a model-form, based on the UnitUser model.
The fields of the form are based on the fields of the model.
Since UnitUser doesn't contain details about the user, but merely a single reference to the user model, the user model itself is only represented via a single field. The string representation of a user model is the username. I would think that's the reason you see the username in your form.
In short: Your model-form considers the user just as a single field and will use the string representation of the value of that field as init-value.
If you want to display further attributes of the user in your form, you might have to construct a specific form for that purpose.

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.

Django Forms Newbie Question

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>

Categories