I have a customuser model with
class customuser(AbstractUser):
# additional fields
def __str__(self):
return self.username
I have another model, that becomes the foreign key for this model
class bfs_support_ticket_model(models.Model):
ticket_created_by = models.ForeignKey(customuser, on_delete = models.CASCADE)
Why doesn't Django renders username for the form, but render or returns username everywhere correctly
class ticket_edit_form(ticket_create_form):
# ticket_created_by = forms.CharField(widget=forms.TextInput(attrs={'class' : 'form-control', 'readonly' : True})) # does not work in this way
def __init__(self, *args, **kwargs):
super(ticket_edit_form,self).__init__(*args, **kwargs)
# self.fields['ticket_created_by'].disabled = True
self.fields['ticket_created_by'].widget = forms.TextInput(attrs={'class' : 'form-control', 'readonly' : True}) # doesnot work in this way too
class Meta:
model=bfs_support_ticket_model
exclude=['ticket_last_updated_by']
When the form is rendered it just prints the customuser.id instead of customuser.username
But when no form initialization is made, it return the customuser.username correctly
i.e. when
class ticket_edit_form(ticket_create_form):
def __init__(self, *args, **kwargs):
super(ticket_edit_form,self).__init__(*args, **kwargs)
self.fields['ticket_created_by'].disabled = True # only this line present it renders customuser.username
class Meta:
model=bfs_support_ticket_model
exclude=['ticket_last_updated_by']
Please help me, where I am going wrong
Edit:
Why does
self.fields['ticket_created_by'].disabled = True # prints username
while
self.fields['ticket_created_by'].widget = forms.TextInput(attrs={'class' : 'form-control', 'readonly' : True}) # this doesn't
class bfs_support_ticket_model(models.Model):
ticket_created_by = models.ForeignKey(customuser, on_delete = models.CASCADE)
def __str__(self):
return self.ticket_created_by.customuser.username
Observations:
Since the field is a Foreignkey, it gets rendered as a modelchoicefield, hence initializing the field as charfield, only prints the exact value present in the db (in this case customuser.id)
To make it render properly, I had to initialize it as a modelchoicefield, like
ticket_created_by = forms.ModelChoiceField(queryset = customuser.objects.all(), widget = forms.Select(attrs={'class' : 'custom-select custom-select-sm mb-3', 'disabled' : True}))
Please advice, if this is a correct solution
Related
the form does not pass validation and throws an error, although the correct data is entered, what is the problem?
I enter in the input field +79211234569 and gives an error in html Select the correct option. Your option is not among the valid values.
form data: <'form': <RelatedAddForm bound=True, valid=False, fields=(name;phone)>
forms
class ListTextWidget(forms.Select):
template_name = 'include/_forms_clients_datalist.html'
def format_value(self, value):
# Copied from forms.Input - makes sure value is rendered properly
if value == '' or value is None:
print('ListTextWidget None')
return ''
if self.is_localized:
print('ListTextWidget local')
return formats.localize_input(value)
return str(value)
class ChoiceTxtField(forms.ModelChoiceField):
widget=ListTextWidget()
class RelatedAddForm(forms.ModelForm):
phone = ChoiceTxtField(queryset=Clients.objects.order_by('-phone'))
class Meta:
model = Clients
fields = ['name', 'phone']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'off'}),
}
models
class Clients(models.Model):
name = models.CharField(max_length=150, blank=True, verbose_name='Имя')
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed.")
phone = models.CharField(validators=[phone_regex], unique=True, max_length=17, verbose_name='Телефон')
def get_absolute_url(self):
return reverse('view_clients', kwargs={'pk': self.pk})
def __str__(self):
return self.phone
UPD:
request_post <QueryDict: {'csrfmiddlewaretoken': ['m9mKTv4kLWSFmW6Jj39OUAZ0zINBoFjvphjYADWvY97lk1oKAB3LAHhxOpmXnKbo'], 'cli ents-name': ['test'], 'clients-phone': ['+79121234566']}> passes the name variable and the phone variable in the correct format
Most likely the problem is that the check of the phone field is related to the ChoiceTxtField?
ModelChoiceField assumes selection of a previously saved model only (specified in the queryset parameter). There is no need to inherit ModelChoiceField, changes are made at the widget level. Therefore, you need to replace it with CharField.
class ListTextWidget(forms.Select):
template_name = 'include/_forms_clients_datalist.html'
class PhoneInputField(forms.CharField):
widget=ListTextWidget()
class RelatedAddForm(forms.ModelForm):
phone = PhoneInputField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['phone'].choices = Clients.objects.order_by('-phone').values_list('phone')
class Meta:
model = Clients
fields = ['name', 'phone']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'off'}),
}
I have a form that has two fields (mail and status). I want to have the status field hidden only when the user has a profile not equal to "tech" so that the user cannot change its value in that case.
What I was trying to do, but I still didn't get it to work since it throws me TypeError error: __init __ () got an unexpected keyword argument 'user', is to overwrite the __init __ () method of RequestForm in forms.py and on the other hand, overwrite the get_form_kwargs () method to pass the user to the form. I post the code that I understand relevant:
views.py:
...
class RequestUpdate(UpdateView):
model = Request
form_class = RequestForm
template_name = 'request/request_form.html'
success_url = reverse_lazy('request:request_list')
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
forms.py:
class RequestForm(forms.ModelForm):
class Meta:
model = Request
fields = [
'mail',
'status',
]
labels = {
'mail': 'E-Mail (including #domain.example)',
'status': "Request's status:"
}
widgets = {
'mail': forms.EmailInput(attrs={'class': 'form-control'}),
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if self.user.is_tech == False:
self.fields.pop('status')
models.py
...
class Request(models.Model):
mail = models.CharField(max_length=50)
states = [('1','Pending'), ('2','In process'), ('3','Done')]
status = models.CharField(max_length=50, choices=states, default='1')
user = models.ManyToManyField(Requester, blank=True)
Why does the * unexpected keyword argument 'user' * error occur? What suggestions could you make to hide that field according to the user's profile?
I would suggest to only override get_form method as follow in RequestUpdate:
def get_form(self, *args, **kwargs):
form = super().get_form(self.form_class)
if not self.request.user.status.is_tech:
form.fields.pop('status')
return form
I don't know why your code fails, but anyway you can tweak your form in view class so no need to do more work.
I am trying to change the required of a form field to 'False' depending on the input of another field.
form:
class MyNewForm(forms.Form):
def __init__(self, *args, **kwargs):
self.users = kwargs.pop('users', None)
super(MyNewForm, self).__init__(*args, **kwargs)
self.fields['name'] = forms.CharField(
max_length=60,
required=True,
label="Name *:",
widget=forms.TextInput(
attrs={'class' : 'form-control', 'placeholder': 'Name'})
)
def clean_this_other_field(self):
# change required field
name = self.fields['name']
print name.required
name.required=False
print name.required
results of print:
True
False
So the field required is changing to 'False' but when the form page reloads it reverts back to 'True'
Any suggestions?
EDIT - Problem solved
For anyone landing on this with a similar issue:
def clean_remove(self):
cleaned_data = super(MyNewForm, self).clean()
remove = cleaned_data.get('remove', None)
if remove:
self.fields['name'].required=False
Remove is the first field in the form so adding this clean on the remove field resolves the issue.
Can you give more of your code when this problem appers?
If u use ModelForm you can do something like:
class YourForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(YourForm, self).__init__(*args, **kwargs)
self.fields['name'].required = True
class Meta:
model = YouModel
fields = (...)
Django Model Forms - Setting a required field
U can add required using widget too:
email = forms.EmailField(
max_length=100,
required=True,
widget=forms.TextInput(attrs={ 'required': 'true' }),
)
I use modelformset_factory, and I use full_clean() to validate the form with unique_together=True. I wonder what is the best way to handle error in case the unique_together do not validate in order to return the error message in the template.
Please take a look to my view, and tell me if im correct the way I do it, or if there is a better approach.
model:
class Attribute(models.Model):
shapefile = models.ForeignKey(Shapefile)
name = models.CharField(max_length=255, db_index=True)
type = models.IntegerField()
width = models.IntegerField()
precision = models.IntegerField()
def __unicode__(self):
return self.name
def delete(self):
shapefile = self.shapefile
feature_selected = Feature.objectshstore.filter(shapefile=shapefile)
feature_selected.hremove('attribute_value', self.name)
super(Attribute, self).delete()
class Meta:
unique_together = (('name', 'shapefile'),)
form:
class AttributeForm(ModelForm):
def __init__(self, *args, **kwargs):
super(AttributeForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['type'].widget.attrs['disabled'] = True
self.fields['type'].required = False
self.fields['width'].widget.attrs['readonly'] = True
self.fields['precision'].widget.attrs['readonly'] = True
def clean_type(self):
if self.instance and self.instance.pk:
return self.instance.type
else:
return self.cleaned_data['type']
type = forms.ChoiceField(choices=FIELD_TYPE)
class Meta:
model = Attribute
exclude = 'shapefile'
view:
def editFields(request, shapefile_id):
layer_selected = Shapefile.objects.get(pk=shapefile_id)
attributes_selected= Attribute.objects.filter(shapefile__pk=shapefile_id)
attributesFormset = modelformset_factory(Attribute, form=AttributeForm, extra=1, can_delete=True)
if request.POST:
formset = attributesFormset(request.POST, queryset=attributes_selected)
if formset.is_valid():
instances = formset.save(commit=False)
for instance in instances:
instance.shapefile = layer_selected
try:
instance.full_clean()
except ValidationError as e:
non_field_errors = e.message_dict[NON_FIELD_ERRORS]
print non_field_errors
formset = attributesFormset(queryset=attributes_selected)
return render_to_response("basqui/manage_layer_editFields.html", {'shapefile': layer_selected, 'formset':formset}, context_instance=RequestContext(request))
instance.save()
formset = attributesFormset(queryset=attributes_selected)
return render_to_response("basqui/manage_layer_editFields.html", {'shapefile': layer_selected, 'formset':formset}, context_instance=RequestContext(request))
The disadvantage of your approach is that you have moved the validation from the form to the view.
I had the same problem recently of validating a unique together constraint where one field is excluded from the model form. My solution was to override the model form's clean method, and query the database to check the unique together constraint. This duplicates the code that is called by full_clean, but I like it because it's explicit.
I briefly thought about overriding _get_validation_exclusions which would have been more DRY, but I decided not to rely on a private api.
models.py:
class Tag(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
user = models.ForeignKey(User)
tag = models.ManyToManyField(Tag)
title = models.CharField(max_length=100)
content = models.TextField()
created = models.DateTimeField(default=datetime.datetime.now)
modified = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
return '%s,%s' % (self.title,self.content)
class PostModelForm(forms.ModelForm):
class Meta:
model = Post
class PostModelFormNormalUser(forms.ModelForm):
class Meta:
model = Post
widgets = { 'tag' : TextInput() }
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None
views.py:
if request.method == 'POST':
form = PostModelFormNormalUser(request.POST)
print form
print form.errors
tagstring = form.data['tag']
splitedtag = tagstring.split()
if form.is_valid():
temp = form.save(commit=False)
temp.user_id = user.id
temp.save()
l = len(splitedtag)
for i in range(l):
obj = Tag(name=splitedtag[i])
obj.save()
post.tag_set.add(obj)
post = Post.objects.get(id=temp.id)
return HttpResponseRedirect('/viewpost/' + str(post.id))
else:
form = PostModelFormNormalUser()
context = {'form':form}
return render_to_response('addpost.html', context, context_instance=RequestContext(request))
Here form.is_valid() is always false because it gets the tag as string from form. But it expects list as form.data['tag'] input. Can anyone tell me how can i fix it?
How can i write a custom widget to solve this?
I don't think you need a custom widget (you still want a TextInput), you want a custom Field. To do this, you should subclass django.forms.Field. Unfortunately the documentation is scant on this topic:
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).
I found this blog post that covers both custom widgets and fields in more depth. The author disagrees with the documentation I quoted above - it's worth reading over.
For your specific situation, you would do something like this (untested):
class MyTagField(forms.Field):
default_error_messages = {
'some_error': _(u'This is a message re: the somr_error!'),
}
def to_python(self, value):
# put code here to coerce 'value' (raw data from your TextInput)
# into the form your code will want (a list of Tag objects, perhaps)
def validate(self, value):
if <not valid for some reason>:
raise ValidationError(self.error_messages['some_error'])
Then in your ModelForm:
class PostModelFormNormalUser(forms.ModelForm):
tag = MyTagField()
class Meta:
model = Post
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None