Django form not calling clean_<fieldname> (in this case clean_email) - python

I couldn't find an answer to the following question, it took me a couple of hours to find out, hence I'm adding it. I'll add my approach of solving it and the answer.
I'm following a YouTube tutorial from This person. For some reason I'm typing the same code, and I checked every single letter. Yet for some reason my cleaning functions aren't called. It's probably something simple, especially since a related question showed something similar. It's probably a framework thing that I get wrong, but I wouldn't know what it is.
Here is the relevant code.
forms.py (complete copy/paste from his Github)
from django import forms
from .models import SignUp
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField()
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['full_name', 'email']
### exclude = ['full_name']
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split('.')
# if not domain == 'USC':
# raise forms.ValidationError("Please make sure you use your USC email.")
if not extension == "edu":
raise forms.ValidationError("Please use a valid .EDU email address")
return email
# Final part is ommited, since it's not relevant.
admin.py (typed over from the tutorial)
from django.contrib import admin
# Register your models here.
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['__unicode__', 'timestamp', 'updated']
class Meta:
model = SignUp
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)

After using print statements for a while and reading questions that seemed similar but eventually didn't solve my problem, I decided to look into the source of Django (idea inspired by the most similar question I could find).
Then, I decided to debug the source, since I wanted to know how Django is treating my customized function (thanks to a tutorial + SO answer). In the source I found that the customized functions were called around return super(EmailField, self).clean(value) (line 585, django/forms/fields.py, Django 1.8). When I was stepping through the code I found the critical line if hasattr(self, 'clean_%s' % name): (line 409, django/forms/forms.py, Django 1.8). I checked for the value name which was "email". Yet, the if-statement evaluated as False ((Pdb) p hasattr(self, 'clean_%s' % name)). I didn't understand why, until I figured out that the function name was not registered ((Pdb) pp dir(self)).
I decided to take a look at the whole source code repository and cross-checked every file and then I found that
class Meta:
model = SignUp
form = SignUpForm
means that form / SignUpForm were nested inside the Meta class. At first, I didn't think much of it but slowly I started to realize that it should be outside the Meta class while staying main class (SignUpAdmin).
So form = SignUpForm should have been idented one tab back. For me, as a Django beginner, it still kind of baffles me, because I thought the Meta class was supposed to encapsulate both types of data (models and forms). Apparently it shouldn't, that's what I got wrong.

Related

Am I able to create a form that is able to add a new django.contrib.auth User without logging in to the admin panel?

Have created a form but unsure if is right and also unable to add a user, it will show TypeError/
This is how the form I want it to look like
The following is my coding:
class Form_Add_User(forms.Form):
name=forms.CharField(label="Name", max_length=50)
dateofbirth=forms.DateField(label="Date of Birth", widget=forms.widgets.DateInput(format="%m/%d/%Y"))
contactnum=forms.CharField(label="Contact Number", max_length=9)
def adduser(request):
if len(request.POST)>0:
form=Form_Add_User(request.POST)
if(form.is_valid()):
name=form.cleaned_data['name']
dateofbirth=form.cleaned_data['dateofbirth']
contactnum=form.cleaned_data['contactnum']
new_user=User(name=name,dateofbirth=dateofbirth,contactnum=contactnum)
new_user.save()
return redirect('/adduser')
else:
return render(request,'adduser.html',{'form':form})
else:
form=Form_Add_User
return render(request,'adduser.html',{'form':form})
First off: it's always very useful to also post a full error message if you have one. The more info you give us, the easier (and quicker!) is answering your question.
I assume, your User model is not actually django's auth User model (see, if you had posted the model, I wouldn't have to guess).
The form you pass to your template was not instantiated:
#...
else:
form=Form_Add_User() #!!
return render(request,'adduser.html',{'form':form})

Django: get_fieldsets Unknown fields

I'm facing a little situation here. I have a model named Product that contains basic information about the product, and a model named SpecificProductInfo that contains a pk pointing towards a Product, a pk pointing towards a Language (another model) and other information in the given language
Not only it's not possible to create a SpecificProductInfo by yourself, but whenever you create a Product (in the admin), the SpecificProductInfo fields should appear too (you can't create a product if you don't give its name in every language used by your website for example). For this, I wanted to use the 'get_fieldsets' function. But Im meeting this error:
'Unknown field(s) (name-Chinese, name-English) specified for Product. Check fields/fieldsets/exclude attributes of class ProductAdmin'
The weirdest thing is that it happens one every two times. When I load the admin page, I don't have any mistakes. Then the mistake appears when I click on save. But if I change a meaningless thing in my code (for example I add a space and then I remove it and save the code) and reload the page, it works. But then the next time it won't! I just don't get it!
Here is my code! Thank you for helping me figure it out!
language_fields = collections.OrderedDict()
settings = SiteSettings.objects.get(pk=1)
class ProductAdminForm(forms.ModelForm):
class Meta():
fields = ('picture', 'phone', 'email', 'website', 'notes',)
model=Product
def __init__(self, *args,**kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
for lang in sitesettings.active_languages.all():
name_title = 'name-{0}'.format(lang.code)
name_label = 'Name ({0})'.format(lang.name)
language_fields[lang.id] = (name_title, )
self.fields[name_title] = forms.CharField(label=name_label,max_length=255)
class ProductAdmin(admin.ModelAdmin):
form=ProductAdminForm
def get_fieldsets(self, request, obj=None):
fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
language_index = 1
for lang in language_fields.values():
fieldsets[0][1]['fields'].insert(language_index, lang[0])
language_index = language_index + 1
return fieldsets
admin.site.register (Product, ProductAdmin)
PS: Im working on Django 1.6.0 and Python 3.2.5
PPS: I didnt specify the includes
PPPS: Settings is a model where one of the fields is the active languages (M2M Language).
Thank you very much for your help!
Have a great day

Django ModelForm not calling clean

I am performing a basic Django ModelForm create/validate/save operation. My custom clean methods are not being called when is_valid() is being called when running the code under the Eclipse debugger and I set a breakpoint after the form creation and the call to is_valid().
I have traced through the Django base code numerous times and it appears that the error dictionary on the ModelForm class is never set to None, which triggers the validation. I suspect that this is due to an interaction with the debugger accessing the _errors attribute of the ModelForm to display in the variables pane.
When I remove all breakpoints and let the code flow naturally, I can prove that the custom clean code is running by issuing print statements.
Is this a flaw in the Django ModelForm design, an Eclipse problem or am I barking up the wrong tree?
models.py:
from django.db import models
class TestModel1(models.Model):
field1 = models.CharField(max_length=45)
field2 = models.IntegerField(default=2)
field3 = models.CharField(max_length=45, null=True, blank=True)
forms.py:
from order.models import TestModel1
from django.forms import ModelForm
class OrderTestForm(ModelForm):
def clean_field1(self):
return self.cleaned_data['field1']
def clean_field2(self):
return self.cleaned_data['field2']
class Meta:
model = TestModel1
My test harness:
from forms import OrderTestForm
row = {'field1': 'test value', 'field2': '4', }
ff = OrderTestForm(row)
#ff.full_clean()
if ff.is_valid():
ff.save()
else:
print ff.errors
I have found the "answer" which is sort of a non-answer. The exact use case was receiving a CSV file from a customer. After inspection of the customer's actual data file, the data fields were padded with spaces -- many spaces. I trimmed the input and shoved that trimmed dictionary into the form and everything worked. Still does not explain why eclipse choked on this.
I had the same problem and tried to dig a little deeper and debug the framework.
There's probably a difference between regular forms and model forms which causes this not to work, but this hack (overriding is_valid() instead of clean(...)) worked for me:
def is_valid(self):
#run whatever ModelForm validations you need
return super(OrderTestForm, self).is_valid()
What if you try:
from order.models import TestModel1
from django.forms import ModelForm
class OrderTestForm(ModelForm):
class Meta:
model = TestModel1
def clean_field1(self):
value = self.cleaned_data['field1']
print value
return value
def clean_field2(self):
value = self.cleaned_data['field2']
print value
return value

Django custom registration fields

I'm becoming increasingly bewildered by the range of answers on offer to the seemingly simple problem of adding custom fields to the django-registration register form/flow. This should be a default, documented aspect of the package (not to sound ungrateful, just that it is such a well-equipped package), but solutions to the problem are dizzying.
Can anyone give me the most simple solution to getting UserProfile model data included in the default registration register page?
Update:
I eventually used Django Registration's own signals to give me this hacky fix. It is particularly ugly because, I had to use try on the POST attribute dealing with my Boolean since I found that the checkbox returned nothing if left empty.
Would appreciate any advice on improving this, or best practice.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
Thanks
What you have looks like a workable approach.
I've looked through the django-registration code, and based on the following comments in the register view I've come up with another solution. I'm not totally sure this is cleaner, but if you aren't a fan of signals this is good. This also provides a much easier avenue if you intend to make more customizations.
# from registration.views.register:
"""
...
2. The form to use for account registration will be obtained by
calling the backend's ``get_form_class()`` method, passing the
``HttpRequest``. To override this, see the list of optional
arguments for this view (below).
3. If valid, the form's ``cleaned_data`` will be passed (as
keyword arguments, and along with the ``HttpRequest``) to the
backend's ``register()`` method, which should return the new
``User`` object.
...
"""
You could create a custom backend and override those mentioned methods:
# extend the provided form to get those fields and the validation for free
class CustomRegistrationForm(registration.forms.RegistrationForm):
receive_email = forms.BooleanField(initial=True, required=False)
# again, extend the default backend to get most of the functionality for free
class RegistrationBackend(registration.backends.default.DefaultBackend):
# provide your custom form to the registration view
def get_form_class(self, request):
return CustomRegistrationForm
# replace what you're doing in the signal handler here
def register(self, request, **kwargs):
new_user = super(RegistrationBackend, self).register(request, **kwargs)
# do your profile stuff here
# the form's cleaned_data is available as kwargs to this method
profile = new_user.userprofile
# use .get as a more concise alternative to try/except around [] access
profile.receive_email = kwargs.get('receive_email', False)
profile.save()
return new_user
To use the custom backend, you can then provide separate urls. Before including the default urls, write 2 confs that point at your custom backend. Urls are tested in the order defined, so if you define these two before including the defaults, these two will capture before the default ones are tested.
url(r'^accounts/activate/(?P<activation_key>\w+)/$',
activate,
{'backend': 'my.app.RegistrationBackend'},
name='registration_activate'),
url(r'^accounts/register/$',
register,
{'backend': 'my.app.RegistrationBackend'},
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
The docs actually describe all this, but they aren't particularly accessible (no readthedocs). They are all included in the project, and I was browsing them here.
I eventually used Django Registration's own signals to give me this fix.
I will clean up the try/except flow at some point. dokkaebi also points out above that I might be able to assess the request.GET parameters for when a checkbox is left empty.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)

Django Tastypie Override URL with slug

I have a similar coce:
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<slug>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
]
Which produces an URL like:
/api/v1/nodes/<slug>/
Everything fine except that self.get_resource_uri(bundle) returns /api/v1/nodes/<id>/ and I cannot compare the current URL with the resource URI effectively.
What am I doing wrong?
Solution: working code
I implemented the proposed solution here:
https://github.com/ninuxorg/nodeshot/blob/refactoring/nodeshot/core/base/resources.py
Any additional feedback for improvement is welcome.
You could override get_resource_uri on your resource to return the correct uri. After all, the correct one is the one with the slug since that is the (first) one captured by your resource.
Update
The right way to do this is actually to skip override_urls and put this on the Resource's Meta:
detail_uri_name = 'slug'
TLDR Background
I dug a bit deeper and it looks like there's a much better place to implement this. The default implementation of get_resource_uri (indirectly) calls self.detail_uri_kwargs and then reverse.
The default implementation of detail_uri_kwargs in ModelResource just looks up self._meta.detail_uri_name (whose name is not intuitive), and grabs that key off the model. detail_uri_name defaults to pk.
If you just provide a different name here, you can skip the override_urls and the get_resource_uri!
Something like this (building on the code linked in comments by the OP):
from tastypie.resources import ModelResource
from tastypie.bundle import Bundle
class BaseSlugResource(ModelResource):
""" Base Model Resource using slug urls """
class Meta:
abstract = True
detail_uri_name = 'slug'
I'm not sure off the top of my head whether resource Metas are inherited (I'm going to guess they're not), so this may not work as a base class. Luckily, the one line required is probably fine to paste into each Resource that needs it.
I'd like to clean it up with a working example even though the #dokkaebi's answer is marked (and partially) correct. The only missing part is you still have to prepend url that will be resolved for listing and such.
from tastypie.resources import ModelResource
from myapp.models import SomeModel
class BaseSlugResource(ModelResource):
""" Base Model Resource using slug urls """
class Meta:
queryset = SomeModel.objects.all()
detail_uri_name = 'slug'
def prepend_urls(self):
return [
url(r'^(?P<resource_name>%s)/(?P<slug>[\w\.-]+)/$' % self._meta.resource_name, self.wrap_view('dispatch_detail'), name='api_dispatch_detail'),
]
This will display the correct resource_uri's for listing as well as resource get. However, you'll most likely loose {schema} resource (i.e. /api/posts/schema/) as it's treated as a slug too.

Categories

Resources