Django TextField max_length validation for ModelForm - django

Django does not respect the max_length attribute of TextField model field while validating a ModelForm.
So I define a LimitedTextField inherited from the models.TextField and added validation bits similar to models.CharField:
from django.core import validators
class LimitedTextField(models.TextField):
def __init__(self, *args, **kwargs):
super(LimitedTextField, self).__init__(*args, **kwargs)
self.max_length = kwargs.get('max_length')
if self.max_length:
self.validators.append(validators.MaxLengthValidator(self.max_length))
def formfield(self, **kwargs):
defaults = {'max_length': self.max_length}
defaults.update(kwargs)
return super(LimitedTextField, self).formfield(**defaults)
But this still has no affect on ModelForm validation.
What am I missing?

As of Django 1.2 this can be done by validators at model level, as explained here:
https://docs.djangoproject.com/en/stable/ref/validators/
from django.core.validators import MaxLengthValidator
class Comment(models.Model):
comment = models.TextField(validators=[MaxLengthValidator(200)])
Since Django 1.7, you can use max_length which is only enforced in client side. See here

You can enforce a max length for a TextField by defining a CharField with a Textarea widget like this:
class MyClass(models.Model):
textfield = models.TextField()
class MyForm(forms.ModelForm):
textfield = forms.CharField(
max_length = 50,
widget = forms.Textarea
)
class Meta:
model = MyClass
fields = ('textfield',)

No need to import MaxLengthValidator from validators for Django 2.x
from django.db import models
class Comment(models.Model):
comment = models.TextField(max_length=200)

Related

How to don't save null parameter in table if got parameter with null field in Django

I have table with some parameters like this:
class Education(models.Model):
title = models.CharField(default=None, max_length=100)
content = models.TextField(default=None)
In Django request from client maybe content field equals to NULL. So i want to when content parameter is NULL Django does not save it in database but does not show any errors.
Maybe already this field has data and in the Update request client just want to change title field.
In your form / serializer sets the content field as not required, so if the client doesn't want to update that field, it simply doesn't pass a value.
from django.forms import ModelForm
from .models import Education
class EducationForm(ModelForm):
class Meta:
model = Education
fields = ('title', 'content')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['content'].required = False
I found it, Just like this:
class EducationSerializer(serializers.ModelSerializer):
class Meta:
model = Education
fields = ['title', 'content')
extra_kwargs = {'content': {'required': False}}

Django update modelform with field constraints

Having the following Model:
class Book(models.Model):
name = models.CharField()
author = models.CharField()
date = models.DateField()
class Meta:
unique_together = ('name', 'author')
class BookSerializerWrite(serializers.ModelSerializer):
class Meta:
model = Book
class BookView(ApiView):
def put(self, request, *args, **kwargs):
serializer = BookSerializerWrite(data=request.data)
if serializer.is_valid():
serializer.save()
The view above does not work as the serializer.is_valid() is False.
The message is:
'The fields name, author must make a unique set'
Which is the constraint of the model.
How do I update the model?
I would rather not override the serializer's validation method.
I also cannot access the validated_data for an update as in
https://www.django-rest-framework.org/api-guide/serializers/#saving-instances
as this is empty due to the fact that the form does not validate.
Is there a builtin solution?
You can achieve it using UpdateAPIview
serializers.py
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('name', 'author', 'date')
views.py
from rest_framework.generics import UpdateAPIview
from .serializers import BookSerializer
class BookUpdateView(UpdateAPIView):
serializer_class = BookSerializer
urls.py
from django.urls import path
from . import views
url_patterns = [
path('api/book/<int:pk>/update/', views.BookUpdateView.as_view(), name="book_update"),
]
Now, post your data to above url. It should work.
Reference: https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py

Changing label in auth.User model field

How to change label for User field, e.g. for username? I'm using CustomUser model that inherits AbstractUser, and I want to change label for some fields.
I try do next in CustomUser model, but it's not working. Any ideas?
class CustomUser(AbstractUser):
mobile = models.CharField(max_length=16)
address = models.CharField(max_length=100)
def __init__(self, *args, **kwargs):
super(CustomUser,self).__init__(*args, **kwargs)
self.username.__setattr__('label', 'Some text')
A Django model's fields are moved to Model._meta at class initialization, since Django 1.8, you can access them using the _meta API.
Also, label is an attribute of form fields, the equivalent for model fields is verbose_name.
This should work an Django 1.8
class CustomUser(AbstractUser):
mobile = models.CharField(max_length=16)
address = models.CharField(max_length=100)
def __init__(self, *args, **kwargs):
super(CustomUser,self).__init__(*args, **kwargs)
self._meta.get_field('username').verbose_name = 'Some text'
Before Django 1.8 you can use self._meta.get_field_by_name('username')[0] instead of self._meta.get_field('username').

How to automatically hide all slug fields in ModelForm based forms in Django

I have a model with SlugField. Value of that field is created when the model instance is saved for the first time:
from django.db import models
from django.template.defaultfilters import slugify as default_slugify
class SlugModel(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
def save(self, *args, **kwargs):
if not self.pk:
self.slug = self.slugify(self.name)
return super(SlugModel, self).save(*args, **kwargs)
def slugify(self, tag):
slug = default_slugify(tag)
return slug
If i use that model in ModelForm the slug field is by default displayed.
from django.forms import ModelForm
class SlugModelForm(ModelForm):
class Meta:
model = SlugModel
How to automatically prevent all ModelForms from displaying of all of it's SlugFields without manually specifying ModelForm.exclude or SlugField(editable=False) on each form/field?.
I think you could also extend Lychas response, create a base class and inherit from that one:
class MyModelForm(ModelForm):
class Meta:
exclude = ('slug',)
abstract = True
class AnyForm(MyModelForm):
#more here
This is untested though.
You can exclude fields in the Meta class by assigning field names to exclude:
class Meta:
model = SlugModel
exclude = ('slug',)

Django-Admin: CharField as TextArea

I have
class Cab(models.Model):
name = models.CharField( max_length=20 )
descr = models.CharField( max_length=2000 )
class Cab_Admin(admin.ModelAdmin):
ordering = ('name',)
list_display = ('name','descr', )
# what to write here to make descr using TextArea?
admin.site.register( Cab, Cab_Admin )
how to assign TextArea widget to 'descr' field in admin interface?
upd:
In Admin interface only!
Good idea to use ModelForm.
You will have to create a forms.ModelForm that will describe how you want the descr field to be displayed, and then tell admin.ModelAdmin to use that form. For example:
from django import forms
class CabModelForm( forms.ModelForm ):
descr = forms.CharField( widget=forms.Textarea )
class Meta:
model = Cab
class Cab_Admin( admin.ModelAdmin ):
form = CabModelForm
The form attribute of admin.ModelAdmin is documented in the official Django documentation. Here is one place to look at.
For this case, the best option is probably just to use a TextField instead of CharField in your model. You can also override the formfield_for_dbfield method of your ModelAdmin class:
class CabAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(CabAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'descr':
formfield.widget = forms.Textarea(attrs=formfield.widget.attrs)
return formfield
Ayaz has pretty much spot on, except for a slight change(?!):
class MessageAdminForm(forms.ModelForm):
class Meta:
model = Message
widgets = {
'text': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
}
class MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
admin.site.register(Message, MessageAdmin)
So, you don't need to redefine a field in the ModelForm to change it's widget, just set the widgets dict in Meta.
You don't need to create the form class yourself:
from django.contrib import admin
from django import forms
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
kwargs['widgets'] = {'descr': forms.Textarea}
return super().get_form(request, obj, **kwargs)
admin.site.register(MyModel, MyModelAdmin)
See ModelAdmin.get_form.
You can subclass your own field with needed formfield method:
class CharFieldWithTextarea(models.CharField):
def formfield(self, **kwargs):
kwargs.update({"widget": forms.Textarea})
return super(CharFieldWithTextarea, self).formfield(**kwargs)
This will take affect on all generated forms.
If you are trying to change the Textarea on admin.py, this is the solution that worked for me:
from django import forms
from django.contrib import admin
from django.db import models
from django.forms import TextInput, Textarea
from books.models import Book
class BookForm(forms.ModelForm):
description = forms.CharField( widget=forms.Textarea(attrs={'rows': 5, 'cols': 100}))
class Meta:
model = Book
class BookAdmin(admin.ModelAdmin):
form = BookForm
admin.site.register(Book, BookAdmin)
If you are using a MySQL DB, your column length will usually be autoset to 250 characters, so you will want to run an ALTER TABLE to change the length in you MySQL DB, so that you can take advantage of the new larger Textarea that you have in you Admin Django site.
Instead of a models.CharField, use a models.TextField for descr.
You can use models.TextField for this purpose:
class Sample(models.Model):
field1 = models.CharField(max_length=128)
field2 = models.TextField(max_length=1024*2) # Will be rendered as textarea
Wanted to expand on Carl Meyer's answer, which works perfectly till this date.
I always use TextField instead of CharField (with or without choices) and impose character limits on UI/API side rather than at DB level. To make this work dynamically:
from django import forms
from django.contrib import admin
class BaseAdmin(admin.ModelAdmin):
"""
Base admin capable of forcing widget conversion
"""
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(BaseAdmin, self).formfield_for_dbfield(
db_field, **kwargs)
display_as_charfield = getattr(self, 'display_as_charfield', [])
display_as_choicefield = getattr(self, 'display_as_choicefield', [])
if db_field.name in display_as_charfield:
formfield.widget = forms.TextInput(attrs=formfield.widget.attrs)
elif db_field.name in display_as_choicefield:
formfield.widget = forms.Select(choices=formfield.choices,
attrs=formfield.widget.attrs)
return formfield
I have a model name Post where title, slug & state are TextFields and state has choices. The admin definition looks like:
#admin.register(Post)
class PostAdmin(BaseAdmin):
list_display = ('pk', 'title', 'author', 'org', 'state', 'created',)
search_fields = [
'title',
'author__username',
]
display_as_charfield = ['title', 'slug']
display_as_choicefield = ['state']
Thought others looking for answers might find this useful.