Is there any good articles that explain custom form fields in django, not custom model fields? I couldn't find any through google.
Form fields are easy to customize:
class UpperCaseField(forms.CharField):
def clean(self, value)
try:
return value.upper()
except:
raise ValidationError
basically you just create a class that inherits from the field that most resembles what you want, then rewrite the clean() method so that it returns the value you want. Here is another example:
class MyObjectField(forms.ModelChoiceField):
# in this case, 'value' is a string representing
# the primary key of a MyObject
def clean(self, value):
try:
return MyObject.objects.get(pk=value)
except:
raise ValidationError
custom widgets on the other hand, are a little more useful, but a little more hard to do because there are a few more methods that need to be written so that they work smoothly.
As always with open-source code, you'll learn a great deal by reading the source itself. See the django.forms.fields module to see how all the different form fields are defined - most of them are subclasses of others already, so you can just replicate that and change what you need.
It's not a tutorial, but django's docs talks about this a little:
If the built-in Field classes don't
meet your needs, you can easily create
custom Field classes. To do this, just
create a subclass of
django.forms.Field. Its only
requirements are that it implement a
clean() method and that its __init__()
method accept the core arguments
mentioned above (required, label,
initial, widget, help_text).
You can read about the clean method and see an example at the django docs. Again, not a tutorial, but useful.
I find I am learning a lot by reading the code in some of the the django app projects that are available, such as django-extensions, which override the form fields and are good learning tools (for me, at least). This can help get you started.
Related
I got many serializers named 'InputSerializer' and 'OutputSerializer' which translates to 'Input' and 'Output' schema name in drf-spectacular. This ends up referring the api endpoints to the same schema. Is there a way to override the autogenerated schema names of these serializers without changing the name of the class?
I've run into this a bunch, but never tried to solve it. Looking at the docs I found extended_schema_serializer, which might do what you need. Here is the full api, and the relevant point:
component_name – override default class name extraction
#extended_schema_serializer(component_name="SomeNiceReallyLongId")
class Input(Serializer):
# pass
Its kinda long and ugly, but that can be fixed by a decorator on the decorator :D
Edit:
I ended up implementing this. Here is the small wrapper I wrote. It just makes things shorter and consistent between serializers and fields.
You can use extended_schema_serializer more than once on a serializer, so this won't break anything.
#oapi.name("UserEditRequest")
#extend_schema_serializer(examples=[]) # other settings
class EditSerializer(ModelSerializer):
pass
# oapi.py
from drf_spectacular.utils import set_override as _set_override
def name(val: str):
def decorator(klass):
if issubclass(klass, BaseSerializer):
_set_override(klass, "component_name", val)
elif isinstance(klass, Field):
_set_override(klass, "field_component_name", val)
else:
raise Exception(f"Unhandled class: {klass}")
return klass
return decorator
Understandable, but a unique name has to come from somewhere and spectacular cannot know what is a good name for you other than the classname itself.
Andrew provides the native solution but there is also syntactic sugar for that (compatibility feature with drf-yasg).
class InputSerializer(serializer.Serializer):
class Meta:
ref_name = 'SomeNiceReallyLongId'
https://drf-spectacular.readthedocs.io/en/latest/drf_yasg.html?highlight=ref_name#compatibility
Otherwise I would recommend subclassing AutoSchema and overriding _get_serializer_name and make it work for you.
Basically in a popup (bootstrap) I would like to have all specified pre-populated fields from my model.
I found this code (https://groups.google.com/forum/#!searchin/django-rest-framework/HTMLFormRenderer/django-rest-framework/s24WFvnWMxw/hhmaD6Qw0AMJ)
class CreatePerformanceForm(forms.ModelForm):
model = Performance
fields = ('field1', 'field2')
class PerformanceCreateView(ListCreateAPIView):
serializer_class = PerformanceCreateSerializer
model = Performance
template_name = 'core/perform.html'
def get(self, request, format=None):
data = {'
form': CreatePerformanceForm()
}
return Response(data)
My question is the same.
Is there a way to create the form directly from the serializer so I don't have to create a Django form?
I looked at HTMLFormRenderer, but the DRF doc is quiet poor about this issue.
Thanks,
D
See this issue. Important part:
There are some improvements that could be made there [to HTMLFormRenderer], notably supporting error messaging against fields, and rendering the serializer directly into html without creating a Django form in order to do so [...]
So basically, HTMLFormRenderer also uses Django forms. Also, you are right, the documentation doesn't provide too much support for it. Even more, it seems that this renderer might soon change. See here. Quote:
Note that the template used by the HTMLFormRenderer class, and the context submitted to it may be subject to change. If you need to use this renderer class it is advised that you either make a local copy of the class and templates, or follow the release note on REST framework upgrades closely.
I know this doesn't help much, but for now there is no better way than the way you did it.
I am using django.db.models.fields.DecimalField in one case, but its validation error is quite BAD.
like, when user enters 3,4 instead of 3.4 it says - 'Enter a number'. Well 3,4 is as much as number in some countries as 3.4 is. At least to those who perhaps are not well versed in computer stuff.
So for that reason i am trying to override this fields validation so i could validate it myself.
My problem is - before modelforms clean_my_field() is called, models own validation works and it already raises an error.
So i looked up https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects
After reading this i understood that i could do
def full_clean(self):
super(MyModel, self).full_clean(exclude = 'my_field')
and my_field would be excluded from validation and i could validate it myself in
def clean(self)
pass
#how do i access cleaned data here anyway?
#self.cleaned_data does not exist
#is self.my_field the only way?
But alas - it does not work. self.my_field value is old value in clean() method and cleaned_data is nowhere to be found.
All this makes me think my approach is wrong. I could write my own field which extends django's DecimalField i guess. I thought this approach would work... Can someone clear this up for me as - WHY it does not work. why is that exclude there if it does not work? Django version 1.4.2 by the way.
Alan
Edit: I dug deeper. It seems that even if i override all models cleaning methods and dont use super in them at all - the fields are STILL cleaned at some point and the error is already raised by then.
I guess i will be doing some extending to django.db.models.fields.DecimalField in this case.
An answer about why the exclude is there in the full_clean method would still be nice. Why is it there if it does not work?
I know it's an old question but for the ones that didn't find an answer, what I did was to add localize=True in the ModelAdmin for the Admin Site:
formfield_overrides = {
models.DecimalField: {'localize': True},
}
That will make the FormField locale aware, accepting comma as decimal separator (depending of the current locale, of course). It will also display it localized.
https://docs.djangoproject.com/en/dev/topics/i18n/formatting/#locale-aware-input-in-forms
If you are targeting a single DecimalField or you are writing a custom Form or ModelForm just follow the instructions on the URL.
def views_name(request):
......
field = dec_num(form.cleaned-data['....'])
.........
return render(request, 'page.html', {.....})
def dec_num(value):
return value.replace(",",".")
I am trying to move all business-logic-related validations to models, instead of leaving them in forms. But here I have a tricky situation, for which I like to consult with the SO community.
In my SignupForm (a model form), I have the following field-specific validation to make sure the input email does not exist already.
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
If I were to move this validation to the models, according to the official doc, I would put it in clean() of the corresponding model, ExtendedUser. But the doc also mentions the following:
Any ValidationError exceptions raised by Model.clean() will be stored
in a special key error dictionary key, NON_FIELD_ERRORS, that is used
for errors that are tied to the entire model instead of to a specific
field
That means, with clean(), I cannot associate the errors raised from it with specific fields. I was wondering if models offer something similar to forms' clean_<fieldname>(). If not, where would you put this validation logic and why?
You could convert your clean method into a validator and include it when you declare the field.
Another option is to subclass the model field and override its clean method.
However there is no direct equivalent of defining clean_<field name> methods as you can do for forms. You can't even assign errors to individual fields, as you can do for forms
As stated in the comment I believe you should handle this validation at the modelform level. If you still feel like it would be better to do it closer to the model, and since they can't be changed, I would advise a change directly at the db level:
ALTER TABLE auth_user ADD UNIQUE (email)
Which is the poor way to add the unique=True constraint to the User model without monkey patching auth.
As requested, I think that a good way to go about customizing different forms should be done by inheriting from a base modelform. A good example of this is found in django-registration. The only difference is that instead of the parent form inheriting from forms.Form you would make it a modelForm:
class MyBaseModelForm(ModelForm):
class Meta:
model = MyModel
You could then inherit from it and make different forms from this base model:
class OtherFormWithCustomClean(MyBaseModelForm):
def clean_email(self):
email = self.cleaned_data['email']
if ExtendedUser.objects.filter(email=email).exists():
raise ValidationError('This email address already exists.')
return email
I have dozens of Models, each with ONE associated ModelForm (whose Meta.model refers to the Model in question).
E.g.
class FooModel(Model):
pass
class FooModelForm(ModelForm):
class Meta:
model = FooModel
# current approach using a classmethod
FooModelForm.insert_in_model() # does cls.Meta.model.form = cls
So, obviously, it's easy to find FooModel given FooModelForm. What I want is to know the best way to do the REVERSE: find FooModelForm when I am presented with FooModel or even the string "Foo".
Assume only one ModelForm for each model, although solutions that return multiple are fine.
My current approach is to stash the model in the form class (as shown above), but I'm interested in knowing better approaches especially ones that could compute it centrally (without the final line above).
EDIT: I've reviewed things like Django: Display Generic ModelForm or predefined form but I believe this is a simpler question than those. The Django admin code must do something along the lines of what I seek. But get_model equivalent for ModelForms? suggests that might be voodoo and that it would be best to just do dict['Foo']=FooModelForm or its equivalent to keep track of the association explicitly. Seems repetitious.
If you have under 20 forms, sounds like mapping out a dictionary is the easiest way. Django does this kinda thing internally too.
For ModelForms, django admin just creates them on the fly via modelform_factory, so there is no comparable method to get_model
I do see, your method is bullet proof, but requires a line in ever model def.
If you only have one ModelForm per model, you could potentially iterate through the ModelForm subclasses until you find your form.
find FooModelForm when I am presented
with FooModel or even the string
"Foo".
modelforms = forms.ModelForm.__subclasses__()
def get_modelform(model):
try:
return filter(lambda x:x.Meta.model == model, modelforms)[0]
except IndexError:
print "apparently, there wasn't a ModelForm for your model"
If you want to pull the ModelForm as a string, you'll need to make sure both
app_label and __name__ are correct, which means it will be easier to use get_model('app', 'model') in the function.
You could combine this with your method and automatically place an attribute on your models that point to its ModelForm.
Hook into the class_prepared signal at the top of your apps, find the corresponding ModelForm and attach it to your Model class.
Hope that helps or gives you some ideas.