I have a Django models with a DurationField. I would like that the users cannot put a value over 00:09:59. Unfortunately I could not find any information to do this in the doc : https://docs.djangoproject.com/en/4.1/ref/models/fields/#durationfield
You can add validators for that.
It will look something like:
DurationField(
validators=[MaxValueValidator(datetime.timedelta(minutes=10)]
)
It is not possible with default DurationField.
You can override the clean_%s method in the form:
from django import forms
class YourForm(forms.Form):
duration_field = forms.DurationField()
def clean_duration_field(self):
data = self.cleaned_data['duration_field']
if value > timedelta(minutes=9, seconds=59):
raise forms.ValidationError("....", code="invalid")
return data
If you need to do this validation in many places, you can create a custom Field for overriding the validating method of field:
from django import forms
from django.core.validators import validate_email
class RestrictedDurationField(forms.DurationField):
def __init__(self, limit: timdelta, *args, **kwargs):
super().__init__(*args, **kwargs)
self.limit = limit
def validate(self, value):
super().validate(value)
if value > self.limit:
raise forms.ValidationError("....", code="invalid")
# and usage
class form(Form):
duration_field = RestrictedDurationField(limit=timedelta(minutes=9, seconds=59), ...)
Related
In django 4.1.3
Trying to filter a ForeignKey field query using another selected ForeignKey field value in a ModelForm to limit the filter depending on the selected exhibitors corresponding id.
from django import forms
from .models import Entry, Exhibitor, Pen
class EntryForm(forms.ModelForm):
class Meta:
fields = ('exhibitor', 'pen', 'class_id',)
def __init__(self, *args, **kwargs):
super(EntryForm, self).__init__(*args, **kwargs)
# Can't figure out how to filter
# Using the selected exhibitor field value
self.fields['pen'].queryset = Pen.objects.filter(current_owner_id=1)
# where 1 should be the exhibitor_id
# using an int value for the current_owner_id works to filter the query
# but don't know how to access selected field values, or would this work in a pre_save function?
# tried
self.fields['pen'].queryset = Pen.objects.filter(current_owner_id=self.fields['pen'])
TypeError at /admin/exhibitors/entry/add/ Field 'id' expected a number but got <django.forms.models.ModelChoiceField object at 0x2b630a6b2100>.
You have a ModelForm. It means, your form has always an instance of Entry.
in this case you can do:
Use instance attribute:
class EntryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
... # any staff
exibitor = self.instance.exibitor
... # any staff
Use boundedfield.value method:
class EntryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
... # any staff
exibitor = self['exibitor'].value()
... # any staff
You can get 'exibitor' before and send it in form.__init__:
formentry = EntryForm(.... initial = { ..., 'exibitor': something })
The best way - to change Pen.Queryset in formfield_callback method in ModelForm Factory.
More here: https://docs.djangoproject.com/en/4.1/ref/forms/models/#modelform-factory
Django's django.db.models.URLField uses a django.core.validators.URLValidator:
class URLField(CharField):
default_validators = [validators.URLValidator()]
Since it does not specify the schemes to accept, URLValidator defaults to this set:
schemes = ['http', 'https', 'ftp', 'ftps']
I want my URLField to accept ssh:// URLs, so I tried this:
class SSHURLField(models.URLField):
'''URL field that accepts URLs that start with ssh:// only.'''
default_validators = [URLValidator(schemes=['ssh'])]
However when I try to save a new object with a valid ssh:// URL, I get rejected.
This also happens if I skip inheriting from URLField and inherit from CharField directly: (Edit: Actually this does work after I recreated my database. I'm not sure why the former doesn't work.)
class SSHURLField(models.CharField):
'''URL field that accepts URLs that start with ssh:// only.'''
default_validators = [URLValidator(schemes=['ssh'])]
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 64
super(SSHURLField, self).__init__(*args, **kwargs)
When I use the URLValidator directly in a test, it works:
def test_url(url):
try:
URLValidator(schemes=['ssh'])(url)
return True
except:
return False
>>> test_url('ssh://example.com/')
True
>>> test_url('http://example.com/')
False
As #IainDillingham mentioned in the comments, this is a bug in Django: overriding the default_validator of a subclassed ModelField does not necessarily override the default_validator of the FormField to which that base class is associated.
For your example, django.db.models.URLField, we can see its associated form field[0] is django.forms.fields.URLField[1]. So the workaround here is to also override def formfield(...) for your customized SSHURLField, to reference a custom django.forms.fields.URLField subclass with the same validator, like so:
from django.core import validators
from django.db import models
from django.forms.fields import URLField as FormURLField
class SSHURLFormField(FormURLField):
default_validators = [validators.URLValidator(schemes=['ssh'])]
class SSHURLField(models.URLField):
'''URL field that accepts URLs that start with ssh:// only.'''
default_validators = [validators.URLValidator(schemes=['ssh'])]
def formfield(self, **kwargs):
return super(SSHURLField, self).formfield(**{
'form_class': SSHURLFormField,
})
[0] https://github.com/django/django/blob/e17088a108e604cad23b000a83189fdd02a8a2f9/django/db/models/fields/init.py#L2275,L2293
[1] https://github.com/django/django/blob/e17088a108e604cad23b000a83189fdd02a8a2f9/django/forms/fields.py#L650
I'm using django-autocomplete-light in a django admin application but i cant get choiches correctly filtered for a fk field with limit_choiches_to argument: I still get the entire queryset. here's the code:
# autocomplete_light.py
from django.db.models import Q
import autocomplete_light
from myapp.models import MyClass
from otherapp.models import Deps
class MyClassAutocomplete(autocomplete_light.AutocompleteModelBase):
""" MyClass autocomplete widget class """
choiches = MyModels.objects.filter(
Q(dpt__in=Deps.MAIN_DEPARTMENTS),
Q(user__is_active=True)
)
search_fields = ['^full_name', 'initials']
attrs = {'placeholder': 'Type a name'}
autocomplete_light.register(MyClass, MyClassAutocomplete)
# admin.py
class SampleModelAdminForm(forms.ModelForm):
class Meta:
link_attrs = {'cols': 105, 'rows': 3}
model = SampleModel
def __init__(self, *args, **kwargs):
super(SampleModelAdminForm, self).__init__(
*args, **kwargs
)
self.fields['my_fk'].widget = autocomplete_light.ChoiceWidget(
'MyClassAutocomplete'
)
I also tried to override choices_for_request method in AutocompleteModelBase subclass:
def choices_for_request(self):
return MyModels.objects.filter(
Q(dpt__in=Deps.MAIN_DEPARTMENTS),
Q(user__is_active=True)
)
By this way I have the filtered queryset, but I loose the autocomplete feature (for every word that I type, e.g. 'Es', it starts to show me the choiches from the A letter)
Anybody can help me with that?
thanks
Typo: choiches in
choiches = MyModels.objects.filter(
I'm creating the following custom field based off How to create list field in django
import re
from django.db import models
from django.forms.widgets import TextInput
class ListField(models.TextField):
__metaclass__ = models.SubfieldBase
description = "Stores a python list"
widget = TextInput
def __init__(self, *args, **kwargs):
super(ListField, self).__init__(*args, **kwargs)
def to_python(self, value):
if not value:
return []
return filter(None, re.split(r'\,|\s*', value))
def get_prep_value(self, value):
if value is None:
return value
return ', '.join(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^cflowportal\.utils\.modelutils\.ListField"])
Basically, what I want to achieve is a field where you write something like "1, asd, asdf fdgd", it stores it as such in the database but when retrieved it should return that string as an array and when given an array it should convert it back to a comma-seperated string.
I'm still not sure if what I've written so far works, but I'm having trouble displaying it as an input field and not a textarea even if I've set widget=TextInput.
So, how do I show it in the admin with the same input used by the standard CharField?
How can I customize it so that it displays a comma-separated string when showed on such input, but is given back as a Python List when accessed elsewhere?
Thanks
The following is a method to realize what you want
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=256)
labels = models.TextField()
def get_labels(self):
return self.content.split('\n')
def set_labels(self,value):
if isinstance(value,list) or isinstance(value,tuple) or isinstance(value,set):
content = '\n'.join(value)
else:
content = value
self.content = content
You can regard labels as a ListField, set value use obj.set_labels(list) function, and get value use obj.get_labels()
It act as a List Field, and admin site will run as a normal TextField.
This is what I did, but a better solution is excepted.
and a better way to do this is using save_model in admin.py:
class BlogAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# extra data handling, prevent data convert
obj.save()
We have one application containing models.py which contains n no. of classes that inherits base class.We want to create form which dynamically takes value from user n saves in db but problem is that we want to use django form fields instead of django model forms.
As we know there are some fields missing in django forms such as PositiveIntegerField, CommaSeparetedIntegerFields etc. How can we achieve this using django form fields?
If we write follwing code in shell.
from djnago.db import models
mvar = models.PositiveIntegerFields()
from django import forms
fvar = forms.PositiveIntegerFields()
AttributeError: 'module' object has no attribute 'PositiveIntegerField'
forms.py
from django import forms
class ContextForm(forms.Form):
def __init__(self, rdict, *args, **kwargs):
super(ContextForm, self).__init__(*args, **kwargs)
for key in rdict.keys():
self.fields['%s' % str(key)] = getattr(forms,rdict.get(key))()
rdict = {'address': 'CharField','phone': 'CharField', 'Salary': 'PositiveIntegerField','first name': 'CharField','last name':'CharField'}
Looking at the source, all the field does is call the default form field with a keyword argument: min_value.
class PositiveIntegerField(IntegerField):
description = _("Positive integer")
def get_internal_type(self):
return "PositiveIntegerField"
def formfield(self, **kwargs):
defaults = {'min_value': 0}
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
Therefore what you are looking for is merely
from django import forms
fvar = forms.IntegerField(min_value=0)
fvar.clean(-1)
# ValidationError: [u'Ensure this value is greater than or equal to 0.']
As for CommaSeparatedIntegerField, it looks like a CharField with some django.core.validators.validate_comma_separated_integer_list passed in.
f = forms.CharField(validators=[django.core.validators.validate_comma_separated_integer_list])
f.clean('1,2,3')
All this does is make sure the passed in string is '^[\d,]+$'. The field doesn't even do any python conversions... it doesn't really seem to save much time if just validates form input. Indeed, there's a comment that says "maybe move to contrib". Agreed..
Decided to look into this for fun. Here's a ModelForm generator that overrides model fields with new fields... It doesn't yet handle kwargs. It was just the first method I could think of to do this.. without looking into modelform generation itself. It constructs a regular ModelForm that modifies the form /after/ initialization.
MODEL_FIELD_MAP = {
models.IntegerField: forms.CharField,
# change all IntegerField to forms.CharField
}
def modelform_generator(mymodel):
class MyModelForm(forms.ModelForm):
class Meta:
model = mymodel
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for name, form_field in self.fields.items():
try:
model_field = self._meta.model._meta.get_field_by_name(name)[0]
# is this a model field?
field_override = MODEL_FIELD_MAP.get(model_field.__class__)
# do we have this model field mapped to a form field?
if field_override:
self.fields[name] = field_override()
# set the form field to the target field class
except models.FieldDoesNotExist:
pass
return MyModelForm