Django Model Form clean method not being called - django

I have used a Django model form to create HackathonTeam instances. The issue I am facing here is that custom clean method that I have used is not being called. All the other default validations are happening correctly.
# models.py
class HackathonTeam(models.Model):
name = models.CharField(max_length=30)
leader = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='leader_teams')
hackathon = models.ForeignKey(Hackathon, on_delete=models.CASCADE, related_name='hack_teams')
vacancies = models.PositiveIntegerField(default=0)
current_members = models.ManyToManyField(CustomUser, related_name='member_teams')
skills_required = models.ManyToManyField(Skill, related_name='hack_requirements')
cutoff_date = models.DateTimeField(null=True, blank=True) # Someone may not wish to have a cut-off date
closed = models.BooleanField(default=False)
# forms.py ########
class HackathonTeamForm(forms.ModelForm):
class Meta:
model = HackathonTeam
exclude = ['leader', 'current_members', 'closed']
def clean(self):
print("Inside clean")
cleaned_data = super(HackathonTeamForm, self).clean()
print(cleaned_data)
if HackathonTeam.objects.filter(hackathon=cleaned_data.get("hackathon"),
name=cleaned_data.get("name")).exists():
print(1)
raise forms.ValidationError("A team with the same name already exists.")
return cleaned_data
# views.py #########
#login_required(login_url='users:login')
def add_hackathon_team(request):
if request.method == 'POST':
form = HackathonTeamForm(request.POST)
if form.is_valid():
cd = form.clean()
print(cd)
print("Data is valid")
# form.save()
team = form.save(commit=False)
team.leader = request.user
team.save()
return redirect('users:view_hackathon_team', pk=team.id)
else:
form = HackathonTeamForm()
return render(request, 'users/add_hackathon_team.html', {'form': form})
The print statement in the view is being printed and new Teams are created as well. The only issue is that the clean method is not called and duplicate Teams can be created as well.

inside HackathonTeamForm class add the init function:
def __init__(self, *args, **kwargs):
super(HackathonTeamForm, self).__init__(*args, **kwargs)
you should use self.cleaned_data['field_name'] instead of (to make sure its not causing the problem, since you arent calling super maybe the fields are blank) :
cleaned_data = super(HackathonTeamForm, self).clean()

Related

Django - forms trying to clean unique together, CrispyError

I have a model with a unique together and I want to validate this condition in my modelform. The unique together includes a field that is passed to the form in an init method, the user, and a field that is in the form. I'm having problems with validating a unique together condition.
EDIT
I have modified the code to what you see below
model:
class Objective(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
objective_name = models.CharField(max_length=10)
description = models.CharField(max_length=300)
mode = models.CharField(max_length=2, default='LA')
class Meta:
unique_together = ['user', 'objective_name', 'course']
ordering = ['objective_name']
def __str__(self):
return self.objective_name
The view:
def addobjective(request, course_id):
this_course = get_object_or_404(Course, pk=course_id)
user = request.user
all_courses = Course.objects.filter(user=user)
objective_list = Objective.objects.filter(
course=this_course).order_by('objective_name')
context = {'objective_list': objective_list}
if request.method == 'POST':
form = ObjectiveForm(user, request.POST, my_course=this_course)
if form.is_valid():
obj = form.save(commit=False)
obj.course = this_course
obj.user = user
obj.save()
form = ObjectiveForm(user, my_course=this_course)
context['form'] = form
return redirect('gradebook:addobjective', course_id=this_course.id)
else:
form = ObjectiveForm(user, my_course=this_course)
context['form'] = form
context['this_course'] = this_course
context['all_courses'] = all_courses
return render(request, 'gradebook/objective-form.html', context)
forms.py:
class ObjectiveForm(ModelForm):
def __init__(self, user, *args, **kwargs):
self.request = kwargs.pop('request', None)
my_course = kwargs.pop('my_course')
self.objs = Objective.objects.filter(user=user, course=my_course)
super(ObjectiveForm, self).__init__(*args, **kwargs)
class Meta:
model = Objective
fields = ('objective_name', 'description', 'mode',)
def clean(self):
super(ObjectiveForm, self).clean()
objective_name = self.cleaned_data.get("objective_name")
description = self.cleaned_data.get("description")
mode = self.cleaned_data.get("mode")
if self.objs.filter(objective_name=objective_name).count() > 0:
print("error")
del self.cleaned_data["objective_name"]
del self.cleaned_data["description"]
del self.cleaned_data["mode"]
raise ValidationError(
"This course already has a learning objective with this name.")
return self.cleaned_data
EDIT
The error I know get is |as_crispy_field got passed an invalid or inexistent field. This occurs when I enter in a value for objective_name that is a duplicate. error is printed to the console and then I get the above error. I do not get the ValidationError.
The full traceback can be seen here.
Maybe with the form I do not need the unique together constraint in the model?
Yes, my_course field is not defined in Objective model , so maybe you need to change this line:
form = ObjectiveForm(request.POST, my_course=this_course)
To
form = ObjectiveForm(request.POST, course=this_course)
It turns out that the problem was caused by improper indentation of return redirect('gradebook:addobjective', course_id=this_course.id) after the if form.is_valid():. The return redirect has to be a part of the POST request.

How to retrieve user selections from a previous form

I have several forms that take people through steps and below are the first two and the simplest ones and makes it easy to explain what i am having problem with.
The following two views are login required and contain one form on each. First view is the new_operator where the user fills out a single text input field. Second view is the new_asset where the user fills one text input field as the asset name and selects an operator from the a select/dropdown field. The question is how can i get the form to remember the operator name the user created in the previous form and make it as the default option? To be clear, i still want the user to select any other operator if they choose to do so but i want the option they just created to be the default. Thanks a lot in advance for the help.
First, here are the models:
class OperatorCompany(models.Model):
name = models.CharField(max_length=50, unique=True)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='operator_added_by', null=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = "Operator Company"
verbose_name_plural = "Operator Companies"
def __str__(self):
return self.name
class AssetName(models.Model):
name = models.CharField(max_length=50, unique=True)
operator = models.ForeignKey(OperatorCompany, related_name='asset', on_delete=models.CASCADE)
created_at = models.DateTimeField(default=timezone.now)
created_by = models.ForeignKey(User, related_name='asset_added_by', null=True,
on_delete=models.SET_NULL)
class Meta:
verbose_name = "Asset"
verbose_name_plural = "Assets"
def __str__(self):
return self.name
views.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset')
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})
and following are the forms.py without the init, clean functions and the widgets
class NewOperatorForm(forms.ModelForm):
class Meta:
model = OperatorCompany
fields = ('name',)
class NewAssetForm(forms.ModelForm):
class Meta:
model = AssetName
fields = ('name', 'operator')
To share data between multiple pages, you can use session variables. These are stored on the server and associated to clients according to the session cookie they communicate to the server at every request.
Typically, in the first view, you would add after save():
request.session['latest_created_operator_id'] = newoperator.id
to save in the session the operator id.
And in the second view, after the else,
operator_id = request.session.get('latest_created_operator_id', None)
operator = Operator.objects.filter(id=operator_id).first() # returns None if not found
form = NewAssetForm(initial={'operator': operator})
retrieves the operator and populates the form.
(That's untested code; you may need to edit a bit.)
At a glance, maybe something like this would work.
What you can do is add another URL in urls.py for new_asset which accepts a OperatorCompany id. I don't have your url config but it could be something like:
urls.py
path('wellsurfer/new_asset/<int:operator_id>', new_asset, name='wellsurfer:new_asset_operator')
view.py
def new_operator(request):
if request.method == 'POST':
form = NewOperatorForm(request.POST)
if form.is_valid():
newoperator = form.save(commit=False)
newoperator.created_by = request.user
newoperator.created_at = timezone.now()
newoperator.save()
return redirect('wellsurfer:new_asset', operator_id=newoperator.id)
else:
form = NewOperatorForm()
return render(request, 'wellsurfer/create_new_operator.html', {'create_operator': form})
def new_asset(request, operator_id=None):
if request.method == 'POST':
form = NewAssetForm(request.POST)
if form.is_valid():
newasset = form.save(commit=False)
newasset.created_by = request.user
newasset.created_at = timezone.now()
newasset.save()
return redirect('wellsurfer:new_pad')
else:
form = NewAssetForm()
if operator_id is not None:
operator_company = OperatorCompany.objects.get(pk=operator_id)
form.fields['operator'].initial = operator_company
return render(request, 'wellsurfer/create_new_asset.html', {'create_asset': form})

Want to add data to a form, form is valid, but data not among cleaned_data

Django 1.10
I'm trying to add data to a form programmatically.
class Wiki(models.Model):
related_model = models.CharField(max_length=100, blank=False, null=False, default="")
related_object_id = models.CharField(max_length=100, blank=False, null=False, default="")
article = models.TextField(blank=False, null=False, default="")
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def post(self, request, *args, **kwargs):
related_model = kwargs.get('model')
related_object_id = kwargs.get('pk')
form = self.get_form()
form.data._mutable = True
form.data['related_model'] = related_model
form.data['related_object_id'] = related_object_id
form.data._mutable = False
return super(WikiCreate, self).post(request, *args, **kwargs)
In the post method of the superclass I place a breakpoint:
class ProcessFormView(View):
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid(): # breakpoint
return self.form_valid(form)
else:
return self.form_invalid(form)
Well, what can I see at the breakpoint.
form.data =
Then step in the debugger. form.is_valid() returns true. So, now I can see that: 1) _errors is empty; 2) cleaned_data = {'article': "Some text I've just input."}.
Well, 'related_model' and 'related_object_id' have not appeared in the cleaned data.
Could you help me understand why data from these fields are not saved?
This is because you only have the fields:
fields = ['article']
So there are no other fields on your form other than article. Try adding the other two fields to the fields array. If you want them to be there, but not visible you need to create a custom form and set them to have the hidden widget
This isn't the way to add data to a form submission. You should be adding it to the model instance, not the form, once that has been created in the form_valid method. You shouldn't be overriding post at all.
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def form_valid(self, form):
related_model = self.kwargs.get('model')
related_object_id = self.kwargs.get('pk')
item = form.save(commit=False)
item.related_model = related_model
item.object_id = related_object_id
item.save()
return redirect(self.get_success_url())

Complicated Django ModelForm validation with OneToOneField and customed argument

Sorry for the lengthy question. I have a complicated situation with django modelform validation. I have a model UserProject ready and created many objects. I also have another model Action_Inputs to accept multiple parameters, which is a onetoonefield relation with UserProject. I do need customed input argument for one field of Action_Inputs. But I cannot have the form valided.
models.py
class UserProject(models.Model):
pid = models.CharField(max_length=10, null=False, unique=True)
email = models.EmailField(max_length=254, null=False)
directory = models.CharField(max_length=255)
class Action_Inputs(models.Model):
userproject = models.OneToOneField(UserProject, null=False)
method = models.CharField(max_length=255)
file = models.FileField(upload_to='userdata')
Now I have the following ModelForm which takes a customed input argument jobid, catched from url, which is a string to get back to the previous UserProject pid:
class ActionInputsForm(ModelForm):
def __init__(self, jobid, *args, **kwargs):
super(ActionInputsForm, self).__init__(*args, **kwargs)
self.fields['userproject'].initial = jobid
class Meta:
model = Action_Inputs
fields = ['userproject', 'method', 'file'] # userproject will be hidden
def clean_userproject(self):
userproject = self.cleaned_data['userproject']
if len(userproject) != 10:
raise forms.ValidationError("---PID error.")
return UserProject.objects.get(pid=userproject)
def clean(self):
return self.cleaned_data
In my views.py
def parameters_Inputs(request, jobid):
if request.method == "POST":
form1 = ActionInputsForm(request.POST, request.FILES, jobid)
if form1.is_bound:
form1.save()
return render(request, 'goodlog.html', {'jobid': jobid})
elif request.method == "GET":
form1 = ActionInputsForm(jobid)
return render(request, 'inputsform.html',
{'form1': form1, 'jobid': jobid})
Now the request.POST['userproject'] is empty, which means the jobid has not been modified by init, the request.FILES looks correct but the validation is false. It says Unicode object has no attrite get, which is related to the uploaded file. Any idea about what is wrong? Thanks very much.
The following works:(thanks to Vladimir Danilov)
def __init__(self, jobid, *args, **kwargs):
super(ActionInputsForm, self).__init__(*args, **kwargs)
self.fields['userproject'].initial = UserProject.objects.get(pid=jobid)
def clean_userproject(self):
userproject = self.cleaned_data['userproject']
if not userproject:
raise forms.ValidationError("---UserProject not found.")
return userproject
def parameters_Inputs(request, jobid):
if request.method == "POST":
form1 = ActionInputsForm(jobid, request.POST, request.FILES)
.......
Not answer, but do you mean ActionInputsForm instead of Action_Inputs in these lines?
form1 = Action_Inputs(request.POST, request.FILES, jobid)
# ...
form1 = Action_inputs(jobid)
Also, you should write ActionInputsForm(jobid, request.POST, request.FILES).
Because in your case jobid will be request.POST.

Validating Unique Fields in Django

I don't know if I'm approaching the problem in the right way. The intended outcome is to have a form that displays only name and description. Once the user submits the form I want to add the current user as owner and check if there's already an entry that has the same name and user. If there is, I want to return the form with errors. If not, I want to save Status.
My model:
class Status(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
owner = models.ForeignKey(User)
active = models.BooleanField(default=True)
class Meta:
unique_together = ('name','owner')
My View:
def settings_status(request):
status_form = StatusForm()
if request.method == 'POST':
status_form = StatusForm(request.POST)
if status_form.is_valid():
new_status = Status()
new_status.name = status_form.cleaned_data['name']
new_status.description = status_form.cleaned_data['description']
new_status.owner = request.user
new_status.save()
return render_to_response('base/settings_status.html',{
'status_form' : status_form,
}, context_instance=RequestContext(request))
I have tried numerous things, but I keep running into the problem that if I add owner to the object separately then it isn't available to the model's clean function and therefore can't be used to check if name and owner are unique.
Several ways to do this:
for example, passing in the user (owner) to the form:
forms.py:
class StatusForm(forms.Form):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user','')
super(StatusForm, self).__init__(*args, **kwargs)
self.fields['name'] = forms.CharField(label='Name')
self.fields['description'] = CharField(label='Description', widget=forms.Textarea)
def clean(self):
cleaned_data = self.cleaned_data
name = cleaned_data.get('name')
if Status.objects.filter(name=name, owner=self.user).exists():
self._errors['name'] self.error_class(['Status with this name exists'])
return cleaned_data
views.py:
def settings_status(request):
if request.method == 'POST':
status_form = StatusForm(request.POST, user=request.user)
if status_form.is_valid():
new_status = Status()
new_status.name = status_form.cleaned_data['name']
new_status.description = status_form.cleaned_data['description']
new_status.owner = request.user
new_status.save()
else:
status_form = StatusForm(user=request.user)
context = {'status_form':status_form,}
return render_to_response('base/settings_status.html', context,
context_instance=RequestContext(request))
Also look at setting initial data depending on your form setup and consider using a ModelForm.