Is there a better way to create this Django form? - django

This particular form has 2 boolean fields, if the first field is No/False, then the next two fields become required. This form does work, but it seems very ugly to me.
Without clean_approved, approved is not actually required, meaning the required=True argument is just documentation, not working code
The self._errors dict code MIGHT (not in this case) overwrite any other errors that were present in that field.
With all the other ways Django makes it easy to make beautiful code, I must be doing something wrong for this Boolean field (that should be required) and the optionally required fields based on the answer to the first field.
Please show me the right way to do this.
class JobApprovalForm(forms.Form):
approved = forms.NullBooleanField(required=True)
can_reapply = forms.NullBooleanField(required=False)
reject_reason = forms.CharField(required=False, widget=forms.Textarea())
def clean_approved(self):
""" Without this, approved isn't really required! """
if self.cleaned_data['approved'] == None:
raise forms.ValidationError(_("This field is required."))
return self.cleaned_data['approved']
def clean(self):
""" Make the other two fields required if approved == False """
if not self._errors and self.cleaned_data['approved'] == False:
required_msg = [_("If this job is not approved, this field is required")]
if self.cleaned_data['can_reapply'] == None:
self._errors['can_reapply'] = self.error_class(required_msg)
if not self.cleaned_data['reject_reason']:
self._errors['reject_reason'] = self.error_class(required_msg)
return self.cleaned_data

While mit might seem a bit excessive you could consider using an inline form.
The outer Form contains your "approved" setting.
the inline form handles your rejection objects.
This will allow you to separate two different concerns. First case: approval yes/no. Second case: rejection handling
Unfortunately I can't provide you with example code right now,
but please have a look at this SO Question to get an initial idea of this concept
Inline Form Validation in Django

Related

Separate fields for Users and admins in Django models.py

I have a BooleanField in my models.py file.
I want it to be True for all Admins/Superusers by default, while I want it to be False for all other users by default.
I don't want to use if-else template tags in my html for this purpose, and I am hoping to get a more cleaner solution.
Something like:
field_name = models.BooleanField(
if author.is_superuser:
default = True
else:
default=False
)
Any help is appreciated.
Here the author is the author of the 'Post' which is the model class.
Edit 1: The user.is_superuser which I need is actually the author's user. I need to verify whether the author of a Blog post is a superuser or not, and then assign the default value for the Boolean field.
You don't have any notion of a "current user" in a model definition. But you do have access to the current user in your views (as request.user) so you can use it there to pass the proper initial value for your field
as an example (using function based view):
def myview(request, ....):
if request.method == "POST":
# ...
else:
form = YourModelForm(initial={"field_name": request.user.is_superuser})
EDIT
I don't need the user, but the author's user
What can I say ? If you want better answers, write better questions - you didn't put any context in your question, not even the relevan parts of your model.
This being said, model fields default values are only used in two cases: when creating a new instance, and when adding a new fields to an existing model.
In the first case, your Article or whatever model instance doesn't exist yet, so it cannot have an "author" yet. IOW the "author" is either the request user (most common case and very probably yours), or some other user you explicitely provide. In both cases you do have the right user instance at hand and my answer above applies (either as is or with some adaptation that you're suppoed to be able to do by yourself).
In the second case, that's something you deal with with a data migration, quite simply.

How to have dynamically added fields to specific model fields and not all

i am building an application that has a model with three fields Company,Name, position. in the same model i want to have company name as one field while the user can add name and positions for multiple candidates. the reason am trying to do that is because i didnt find any proper way to set automatically select the foreign key based on the company name entered since foreign key is a drop down list and couldnt figure out the way to make foreign key field equal to company name entered.
appreciate help and suggestions if any for the approach i have in mind.
You need two forms (or more usefully one form and one formset). Use form prefixes to make them distinguishable. Pass both to the template, say as selectform and candidate_formset and in the template, use
{{selectform}}
{{candidate_formset}}
The first is a company-select form. It might, for example, be
class CompanySelectForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['company']
The second is a form, or probably a formset, for entering one, or (via a formset) as many candidates as there are to be entered. It will look like
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
fields = ['name','position']
Now, you use commit=False (docs) to create objects but not save them. First, process CandidateSelectForm, which will give you a Candidate object with a valid company instance, but not save it. Then process the formset of CandidateForm, again with commit=False, which will give you a list of candidate instances with no company, again unsaved. Finally for each candidate in this list, set the company field of every candidate to the one on the object retrieved by CandidateSelectForm and save it.
It will probably be easier to write a plain view function, than messing around with method overrides trying to get the class-based views to process two forms this way.
Edit - added on request.
The view could be modelled on this one in the Django doc. I've made the obvious changes in line with the earlier part of the answer, but it's probably full of errors and I'm not going to debug it further here
def get_name(request):
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create form instances and populate with data from the request:
cs_form = CompanySelectForm(request.POST, prefix="cs")
cand_form = CandidateSelectForm( request.POST, prefix="cand")
# check whether it's valid:
if cs_form.is_valid() and cand_form.is_valid():
selector = cs_form.save(commit=False)
candidate = cand_form(commit=False)
candidate.company = selector.company
candidate.save()
return HttpResponseRedirect('/thanks/')
# if a GET (or any other method) we'll create a blank form
else:
cs_form = CompanySelectForm( prefix='cs')
cand_form = CandidateSelectForm( prefix='cand')
return render(request, 'name.html', {
"select_form": cs_form,
"candidate_form": cand_form,
})
Once you have got this working for a single candidate, you can progress to turning candidate_form into a formset, documented here which will let you enter any number of candidates to be attached to the one selected company.

Modelform - validation on form field

Excuse my newbieness with django, but I am trying to properly validate a formfield named: domainNm
form
class SubmitDomain(ModelForm):
class Meta:
model = Tld #Create form based off Model for Tld
fields = ['domainNm',]
def clean_domainName(self):
cleanedDomainName = self.cleaned_data.get('domainNm')
if Tld.objects.filter(domainNm=cleanedDomainName).count > 1:
errorMsg = u"Sorry that domain is not available."
raise ValidationError(errorMsg)
else:
return self.cleaned_data
## This creates the form.
form = SubmitDomain()
Currently, if I enter in:
abcdefghidfghiqwertyuiopasdfghjklcvbndmsja.com
or
df.com
or
df.com (again)
Both are valid, but they shouldn't be.
It isn't checking if the domain already exists or not (as outlined in the form) - in which it should
I am not getting any errors either.
Any idea what I'm doing wrong?
I see several problems here.
One, your clean method doesn't match the field name. Instead of clean_domainName it needs to be named clean_domainNm. At least as long as that's your model field name.
Two, your comparison if Tld.objects.filter(domainNm=cleanedDomainName).count > 1: is wrong. count is a method - you need to call it.
if Tld.objects.filter(domainNm=cleanedDomainName).count() > 1:
Three, I think your logic is wrong. That will allow the creation of a second instance of a given name, because count() will return 1, meaning the form is valid. I think you want to check the count() against 0, or better yet use exists() instead:
if Tld.objects.filter(domainNm=cleanedDomainName).exists():
Better still, define the field to be unique=True.

Django filter the queryset of ModelChoiceField - what did i do wrong?

I know that many questions exist about this same topic, but i am confused on one point.
My intent is to show two ModelChoiceFields on the form, but not directly tie them to the Game model.
I have the following:
forms.py
class AddGame(forms.ModelForm):
won_lag = forms.ChoiceField(choices=[('1','Home') , ('2', 'Away') ])
home_team = forms.ModelChoiceField(queryset=Player.objects.all())
away_team = forms.ModelChoiceField(queryset=Player.objects.all())
class Meta:
model = Game
fields = ('match', 'match_sequence')
Views.py
def game_add(request, match_id):
game = Game()
try:
match = Match.objects.get(id=match_id)
except Match.DoesNotExist:
# we have no object! do something
pass
game.match = match
# get form
form = AddGame(request.POST or None, instance=game)
form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )
# handle post-back (new or existing; on success nav to game list)
if request.method == 'POST':
if form.is_valid():
form.save()
# redirect to list of games for the specified match
return HttpResponseRedirect(reverse('nine.views.list_games'))
...
Where i am confused is when setting the queryset filter.
First i tried:
form.home_team.queryset = Player.objects.filter(team=match.home_team )
but i got this error
AttributeError at /nine/games/new/1
'AddGame' object has no attribute 'home_team'
...
so i changed it to the following: (after reading other posts)
form.fields['home_team'].queryset = Player.objects.filter(team=match.home_team )
and now it works fine.
So my question is, what is the difference between the two lines? Why did the second one work and not the first? I am sure it is a newbie (i am one) question, but i am baffled.
Any help would be appreciated.
Django Forms are metaclasses:
>>> type(AddGame)
<class 'django.forms.forms.DeclarativeFieldsMetaclass'>
They basically create a form instance according to the information given in its definition. This means, you won't get exactly what you see when you define the AddGame form. When you instantiate it, the metaclass will return the proper instance with the fields provided:
>>> type(AddGame())
<class 'your_app.forms.AddGame'>
So, with the instance, you can access the fields by simply doing form.field. In fact, it is a bit more complicated than that. There are two types of fields you can access. With form['field'] you'll be accessing a BoundField. Which is used for output and raw_input.
By doing form.fields['fields'] you'll be then accessing to a field that python can understand. This is because if the from already got any input, there's where validation and data conversion take places (in fact, those are the fields used for this, the general process of validation is a bit more complicated).
I hope this might clear a little the issue for you but as you may see, the whole form's API is really big and complicated. Is very simple for end-users but it has a lot of programming behind the curtains :)
Reading the links provides will help clear your doubts and will improve your knowledge about this very useful topic and Django in general.
Good luck!
UPDATE: By the way, if you want to learn more about Python's Metaclasses, this is a hell of an answer about the topic.
In you views.py, you have this line:
form = AddGame(request.POST or None, instance=game)
So form is a Form object of class AddGame (Side note: you should change the name to AddGameForm to avoid confusion).
Since home_team is a field in AddGame class, it's not an attribute in form object. That's why you can't access it via form.home_team.
However, Django Form API provides fields attribute to any form object, which is a dict contains all form fields. That's why you can access form.fields['home_team'].
And finally since home_team is a ModelChoiceField, it can contain a queryset attribute, that's why you can access form.fields['home_team'].queryset

Dynamically added form fields are removed in form.cleaned_data

I put some client-side Javascript in my template that allows a user to dynamically add fields to a form. My problem is that these fields are cleaned in form.cleaned_data, so I can't access them that way.
All the fields are accessible in request.POST, so I could just solve this problem with that, but I want to do this the "right way" and I think that the solution lies somewhere in using django forms rather than reading the request directly.
I tried overriding form.clean(), but it seems like the data is already gone by the time it gets there.
Other details: I am naming these fields fieldname_x, where x is a number. In request.POST, request.POST['fieldname'] is a list of a all the values, but form.cleaned_data contains only the last value of each list.
Do you know what type these fields are going to be beforehand? Are they just going to be simple text fields? I've done something similar to this, creating dynamic forms.
# make sure these exist by examining request.POST
custom_fields = ['fieldname_1', 'fieldname_2']
attrs = dict((field, forms.CharField(max_length=100, required=False))
for field in custom_fields)
DynamicForm = type("DynamicForm", (YourBaseForm,), attrs)
submitted_form = DynamicForm(request.POST)
Your submitted form should now contain all the fields you require, along with their values. You might want to remove required=False, but that's up to you.
What this does, is perform a dynamic subclass of your base form, adding the attributes passed in as attrs to the class definition. So when you create an instance with post data, they should be mapped correctly.
Edit:
I read the question a little more closely. What you'll want to do is ensure that your dynamic input elements are named correctly, and the values map to those fieldnames once it reaches django. Otherwise, request.POST will not fill the form in correctly.
<input type='text' name='fieldname_1' value='value_for_field_1' />
etc
It is also possible to do this work in your form file, here is an excellent demonstration by Jacob Kaplan-Mosse for dynamic forms : http://jacobian.org/writing/dynamic-form-generation/ that applies quite well for this problem.
What is done is adding a methods to you form class that add the extra dynamic fields and then yields the information from the clean so that you can get it in your view.
class MyForm(forms.Form):
text = forms.CharField(max_length=30)
def __init__(self, *args, **kwargs):
extra = kwargs.pop('extra')
super(MyForm, self).__init__(*args, **kwargs)
for i, question in enumerate(extra):
self.fields['fieldname_%s' % i] = forms.CharField(label=question)
def extra_fields(self):
for name, value in self.cleaned_data.items():
if name.startswith('fieldname_'):
yield (self.fields[name].label, value)
And to call it from the view :
def doSomething(request, extra_fields):
form = MyForm(request.POST or None, extra=extra_fields)
if form.is_valid():
for (question, answer) in form.extra_answers():
save_answer(request, question, answer)
return redirect("create_user_success")
return render_to_response("template/form.html", {'form': form})
Very neat, congrats to Jacob Kaplan-Moss
I'm doing this in the project I'm currently working on. You'll want to have a look at formsets. But be warned, it isn't pretty. You have to modify this stupid hidden field that stores to the form count, and you have to rename the inputs correctly.. which involves a lot of pointless string parsing. If they had used arrays, like PHP, there wouldn't be an issue, but they're hell bent on making my life miserable... rambles off
Edit:
http://docs.djangoproject.com/en/dev/topics/forms/formsets/#empty-form they provide this which makes your life 5% less painful... you can just do a find and replace on prefix with the appropriate number.. but I think I had an issue where the empty form doesn't contain the proper default values if you initialize the form with post data, so I had to create a 2nd instance without post data to an actually empty empty_form, which is just retarded.