Override Django ModelForm's Clean_unique method - django

I am working on a django app where i have a model which have a field with attribute unique=True. I am trying to save data in this model using ModelForm. My model and Model form is like this.
My models.py
class MyModel(models.Model):
field1 = models.CharField(max_length=40, unique=True)
def __unicode__(self):
return self.field1
class DuplicateFields(models.Model):
field1 = models.CharField(max_length=30)
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def clean_field1(self):
value = self.cleaned_data['field1']
if value :
if MyModel.objects.filter(field1=value).count() > 0:
DuplicateFields.objects.create(field1=value)
return Value
raise forms.ValidationError('this field is required')
**I tried below given code also but it also raise Unique field Exception or error **
def clean_unique(form, field, exclude_initial=True,
value = form.cleaned_data.get(field)
if value:
objs = MyModel.objects.filter(field1=value)
if objs.count() > 0:
DuplicateFields.objects.create(field1=value)
return value
def clean_field1(self):
value=clean_unique(self,'field1')
**My Views.py is **
if request.method=='POST':
form = MyModelForm(request.POST)
if form.is_valid():
cleaned_data = form.cleaned_data
field = cleaned_data['field1']
form.save()
return HttpResponse('form has been saved successfully')
else:
print 'form is invalid'
print form._errors
return render_to_response(template_name, {'form':form}, ci)
else:
return render_to_response(template_name, {'form':form}, ci)
What i want to do is while saving the data or calling form.is_valid() method if i found that the data i am trying to store already exists in model then instead of raising a validation error i want to perform some other logic like will store it in some other model.
But in my view when i am calling 'form.is_valid()` it is returning False. Give me some suggestions. Help will be appreciated

To stop giving validate unique modelform exception what you can do is just override the django ModelForm's validate_unique method. like
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def validate_unique(self):
exclude = self._get_validation_exclusions()
try:
self.instance.validate_unique(exclude=exclude)
except forms.ValidationError as e:
try:
del e.error_dict['field1'] #if field1 unique validation occurs it will be omitted and form.is_valid() method pass
except:
pass
self._update_errors(e) #if there are other errors in the form those will be returned to views and is_valid() method will fail.
and in your view check
if form.is_valid():
field1=form.cleaned_data['form1']
try:
MyModel.objects.get(field1=field1)
#write your logic here for duplicate entry
except:
MyModel.objects.create(field1=field1)
return render_to_response('template.html')
else:
return render_to_response('template.html')

Related

How to check if `MultiSelectField` is empty or not in Django?

In my model I have a field department which is a MultiSelectField and I give the blank=True to that field for some reasons. Now I want to check if user fills the field or not. I have tried to get data from request.POST and gave it a condition using len() function like this if len(field) == 0: but I got an error. Everything works just fine until I added teacher_year = request.POST['teacher_year']
models.py
class CustomUser(AbstractUser):
teacher_department = MultiSelectField(choices=department_choice, blank=True)
forms.py
class TeacherRegisterForm(UserCreationForm):
class Meta(UserCreationForm):
model = CustomUser
fields = ['teacher_year', ...]
views.py
def teacherRegisterView(request):
form = TeacherRegisterForm()
template_name = "attendance/login-register/teacher_register.html"
if request.method == "POST":
form = TeacherRegisterForm(request.POST)
teacher_year = request.POST['teacher_year']
if len(teacher_year) == 0:
messages.warning(request, "Just a remind! You didn't select deparment!")
return redirect('teacher_register')
elif form.is_valid():
form.save()
messages.success(request, "Your account was created! You can log in now.")
return redirect('/')
return render(request, template_name, {'form': form})
the error I got
django.utils.datastructures.MultiValueDictKeyError: 'teacher_year'
MultiValueDict is inherited from normal dict. So you can use get() method with it:
teacher_year = request.POST.get('teacher_year') # if request.POST doesn't contain teacher_year it returns None
if teacher_year:
...

Django model doesn't relate itself to User through ForeignKey

my question is about modelforms, models and instances. After doing some troubleshooting I think my problem is that either the user field from UserFile doesn't associate itself to the auth.User or that the modelform doesn't pass the instance of auth.User. The error is at the dynamic pathing - file_destination - when I try self.user it can't find the user :/
# Model
class UserFile(models.Model):
user = models.ForeignKey('auth.User', related_name='user_file', primary_key=True, unique=True)
user_file = models.FileField(upload_to=file_destination, null=True)
def __unicode__(self):
return self.user_file.name
# View
def login_index(request):
template = 'loginIndex.html'
context = Context()
if request.user.is_authenticated():
if request.method == 'POST':
form = UserUpload(request.POST, request.FILES, instance=request.user)
context.update({'form': form})
if form.is_valid() and form.is_multipart():
instance = UserFile(user_file=request.FILES.get('user_file'))
instance.save()
else:
form = UserUpload()
context.update({'form': form})
return render(request, template, context)
else:
return render(request, template, context)
# Form
class UserUpload(ModelForm):
user_file = forms.FileField(required=False, widget=forms.ClearableFileInput, label='Upload')
class Meta:
model = UserFile
fields = ['user_file']
def clean_user_file(self):
check_user_file = self.cleaned_data.get('user_file')
if check_user_file:
if check_user_file.size > 5120000:
raise ValueError('File is too big for upload')
return check_user_file
# The problem arises when I submit the instance, which saves the file from the form to upload_to=file_destination
# In file_destination I get an error on self.user.username saying || DoesNotExist at "" UserFile has no user.
# My self.user is an None object.
def file_destination(self, filename):
filename = name_generator()
url = "%s/%s/%s" % (self.user.username, 'uploads' ,filename)
return url
You need to manually set the user field on your UserFile instance:
if form.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instanve.save()
form.save_m2m() # add this if you add m2m relationships to `UserFile`
Also, it is a good idea to redirect after the form handling succeeds:
from django.shortcuts import redirect
# ...
return redirect("view-name")

Django 1.3 CreateView/ModelForm: unique_together validation with one field excluded from form

I am looking for a simple answer by example to this common problem. The answers I found so far leave out critical points for us beginners.
I have an app where almost every model has a ForeignKey to User, and there is a unique_together constraint, where one of the fields is always 'user'.
For example:
class SubscriberList(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=70)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (
('user', 'name',),
)
def __unicode__(self):
return self.name
A SubscriberList is always created by a logged in User, and thus in the form to create a Subscriber List, I exclude the user field and give it a value of self.request.user when saving the form, like so:
class SubscriberListCreateView(AuthCreateView):
model = SubscriberList
template_name = "forms/app.html"
form_class = SubscriberListForm
success_url = "/app/lists/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
return super(SubscriberListCreateView, self).form_valid(form)
And here is the accompanying form:
class SubscriberListForm(ModelForm):
class Meta:
model = SubscriberList
exclude = ('user')
With this code, valid data is fine. When I submit data that is not unique_together, I get an Integrity Error from the database. The reason is clear to me - Django doesn't validate the unique_together because the 'user' field is excluded.
How do I change my existing code, still using CreateView, so that submitted data that is not unique_together throws a form validation error, and not an Integrity Error from the db.
Yehonatan's example got me there, but I had to call the messages from within the ValidationError of form_valid, rather than a separate form_invalid function.
This works:
class SubscriberCreateView(AuthCreateView):
model = Subscriber
template_name = "forms/app.html"
form_class = SubscriberForm
success_url = "/app/subscribers/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
try:
self.object.full_clean()
except ValidationError:
#raise ValidationError("No can do, you have used this name before!")
#return self.form_invalid(form)
from django.forms.util import ErrorList
form._errors["email"] = ErrorList([u"You already have an email with that name man."])
return super(SubscriberCreateView, self).form_invalid(form)
return super(SubscriberCreateView, self).form_valid(form)
Taking from the docs at:
https://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects
You should only need to call a model’s full_clean() method if you plan to handle validation errors yourself, or if you have excluded fields from the ModelForm that require validation.
Taking from the docs at:
https://docs.djangoproject.com/en/dev/ref/class-based-views/#formmixin
Views mixing FormMixin must provide an implementation of form_valid() and form_invalid().
This means that in order to view the error (which isn't form related) you'll need to implement your own form_invalid, add the special error message there, and return it.
So, running a full_clean() on your object should raise the unique_together error, so your code could look like this:
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
# validate unique_together constraint
try:
self.object.full_clean()
except ValidationError:
# here you can return the same view with error messages
# e.g.
return self.form_invalid(form)
return super(SubscriberListCreateView, self).form_valid(form)
def form_invalid(self, form):
# using messages
# from django.contrib import messages
# messages.error('You already have a list with that name')
# or adding a custom error
from django.forms.util import ErrorList
form._errors["name"] = ErrorList([u"You already have a list with that name"])
return super(SubscriberListCreateView, self).form_invalid(form)
HTH
adding another example that might be a bit easier for noobs.
forms.py
class GroupItemForm(ModelForm):
def form_valid(self):
self.object = self.save(commit=False)
try:
self.object.full_clean()
except ValidationError:
# here you can return the same view with error messages
# e.g. field level error or...
self._errors["sku"] = self.error_class([u"You already have an email with that name."])
# ... form level error
self.errors['__all__'] = self.error_class(["error msg"]
return False
return True
views.py
def add_stock_item_detail(request, item_id, form_class=GroupItemForm, template_name="myapp/mytemplate.html"):
item = get_object_or_404(Item, pk=item_id)
product = Product(item=item)
if request.method == 'POST':
form = form_class(request.POST, instance=product)
if form.is_valid() and form.form_valid():
form.save()
return HttpResponseRedirect('someurl')
else:
form = form_class(instance=product)
ctx.update({
"form" : form,
})
return render_to_response(template_name, RequestContext(request, ctx))

Django, adding excluded properties to the submitted modelform

I've a modelform and I excluded two fields, the create_date and the created_by fields. Now I get the "Not Null" error when using the save() method because the created_by is empty.
I've tried to add the user id to the form before the save() method like this: form.cleaned_data['created_by'] = 1 and form.cleaned_data['created_by_id'] = 1. But none of this works.
Can someone explain to me how I can 'add' additional stuff to the submitted modelform so that it will save?
class Location(models.Model):
name = models.CharField(max_length = 100)
created_by = models.ForeignKey(User)
create_date = models.DateTimeField(auto_now=True)
class LocationForm(forms.ModelForm):
class Meta:
model = Location
exclude = ('created_by', 'create_date', )
Since you have excluded the fields created_by and create_date in your form, trying to assign them through form.cleaned_data does not make any sense.
Here is what you can do:
If you have a view, you can simply use form.save(commit=False) and then set the value of created_by
def my_view(request):
if request.method == "POST":
form = LocationForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.created_by = request.user
obj.save()
...
...
`
If you are using the Admin, you can override the save_model() method to get the desired result.
class LocationAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.created_by = request.user
obj.save()
Pass a user as a parameter to form constructor, then use it to set created_by field of a model instance:
def add_location(request):
...
form = LocationForm(user=request.user)
...
class LocationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(forms.ModelForm, self).__init__(*args, **kwargs)
self.instance.created_by = user
The correct solution is to pass an instance of the object with pre-filled fields to the model form's constructor. That way the fields will be populated at validation time. Assigning values after form.save() may result in validation errors if fields are required.
LocationForm(request.POST or None, instance=Location(
created_by=request.user,
create_date=datetime.now(),
))
Notice that instance is an unsaved object, so the id will not be assigned until form saves it.
One way to do this is by using form.save(commit=False) (doc)
That will return an object instance of the model class without committing it to the database.
So, your processing might look something like this:
form = some_form(request.POST)
location = form.save(commit=False)
user = User(pk=1)
location.created_by = user
location.create_date = datetime.now()
location.save()

Why is self.instance not set in clean function for a bound form?

I have a Django 1.1 model with unique_together on the owner and title where owner is foreign key on a user. This constraint is enforced but only after the clean. According to Django docs, I should be able to access self.instance to see non-form field object properties of a model instance.
However, I get the error
'JournalForm' object has no attribute 'instance'
Why is self.instance not set on this bound form in either the form clean() or the field clean_title() methods?
My model:
class Journal (models.Model):
owner = models.ForeignKey(User, null=True, related_name='journals')
title = models.CharField(null=False, max_length=256)
published = models.BooleanField(default=False)
class Meta:
unique_together = ("owner", "title")
def __unicode__(self):
return self.title
My form:
class JournalForm (forms.Form):
title = forms.CharField(max_length=256,
label=u'Title:')
html_input = forms.CharField(widget=TinyMCE(attrs={'cols':'85', 'rows':'40'}, ),
label=u'Journal Content:')
published = forms.BooleanField(required=False)
def clean(self):
super(JournalForm, self).clean()
instance = self.instance
return self.cleaned_input
def clean_title(self):
title = self.cleaned_data['title']
if self.is_bound:
if models.Journal.objects.filter(owner.id=self.instance.owner.id, title=title).exclude(id=self.instance.id).count() > 0:
raise forms.ValidationError(u'You already have a Journal with that title. Please change your title so it is unique.')
else:
if models.LabJournal.objects.filter(owner.id=self.instance.owner.id, title=title).count() > 0:
raise forms.ValidationError(u'You already have a Journal with that title. Please change your title so it is unique.')
return title
As requested - the view code:
def journal (request):
try:
journal = models.Journal.objects.get(id=id)
if request.method == 'GET':
if request.user.is_active:
if request.user.id == journal.owner.id:
data = {
'title' : journal.title,
'html_input' : _journal_fields_to_HTML(journal.id),
'published' : journal.published
}
form = forms.JournalForm(initial=data)
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, })
else:
return http.HttpResponseForbidden('<h1>Access denied</h1>')
else:
return _display_login_form(request)
elif request.method == 'POST':
if LOGIN_FORM_KEY in request.POST:
return _handle_login(request)
elif request.user.is_active and request.user.id == journal.owner.id:
form = forms.JournalForm(data=request.POST)
if form.is_valid():
journal.title = form.cleaned_data['title']
journal.published = form.cleaned_data['title'];
journal.save()
if _HTML_to_journal_fields(journal, form.cleaned_data['html_input']):
html_memo = "Save successful."
else:
html_memo = "Unable to save Journal."
return shortcuts.render_to_response('journal/Journal.html', { 'form':form, 'saved':html_memo})
else:
return shortcuts.render_to_response('journal/Journal.html', { 'form':form })
return http.HttpResponseNotAllowed(['GET', 'POST'])
except models.Journal.DoesNotExist:
return http.HttpResponseNotFound('<h1>Requested journal not found</h1>')
Well there are a couple of issues here.
First is that you're not using a ModelForm. The docs you link to are for those, not for standard forms.
Secondly, in order for the form to have an instance attribute, you need to pass that instance in when you're instantiating the form.
If you do use a ModelForm, you won't need the code that converts the journal fields to the form fields, and vice versa on save - the form does that for you. You'll also be able to remove the clean_title method which checks for uniqueness, because that's already defined by the unique_together constraint on the model, and the ModelForm will enforce that for you.