When one option set to True all others set to False (BooleanField) - python

I have this form:
forms.py
class OptionForm(forms.Form):
name = forms.CharField(widget=forms.TextInput(attrs={'class' : 'form-control'}), max_length=100, label='Option Name')
default = forms.BooleanField(label='Default', required=False)
So I can add a new option or edit one that already exist with this two views
views.py
def new_opt(request):
if request.method=='POST':
fnewopt = OptionForm(request.POST)
if fnewopt.is_valid():
model = OptionModel()
model.name = fnewopt.cleaned_data.get('name')
model.default = fnewopt.cleaned_data.get('default')
model.save()
def edit_opt(request, id_opt):
opt = OptionModel.objects.get(id=id_opt)
if request.method == 'POST':
feditopt = OptionForm(request.POST)
if feditopt.is_valid():
model = opt
model.name = feditopt.cleaned_data.get('name')
model.default = feditopt.cleaned_data.get('default')
modelo.save()
But I need that when a user choose one option as default no matter if is new or one that already exist all others options set default to False

You can do that by customizing your model's save() method:
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if self.default:
# At this point self.pk will exist whether
# this was a new or updated instance.
# Using update() allows this to run in a single query
# (plus the query for super().save(), of course).
OptionModel.objects.exclude(pk=self.pk).update(default=False)
A few other notes:
You should wrap your save() method in an atomic transaction.
This will do the extra update() every time the option is saved, not just the first time. To avoid that you'd have to detect when the value was changed to True. There are third-party packages to allow you to do that, but it's probably not worth the bother.
This question and answer doesn't address the validation issue—what if another OptionModel already has default=True?

If your new option has default=True, set all existing options' default=false then set your new option default = true. I'm not sure where your options go when they get saved. You might have a better time finding out which option is true and just setting that false.

Related

Django Form validation error (Select a valid choice) when use "queryset=Product.objects.none()"

I use formset_factory when I get an order for products
product_formset = formset_factory(OrderProductsForm,extra=5)
It works when I use
queryset = Product.objects.all()
in OrderProductsForm(forms.ModelForm):
self.fields['product'] = ModelChoiceField(queryset=Product.objects.all(),empty_label="Ürün Seciniz", widget=forms.Select(attrs={"onChange":'stokKontrol(this.value,this.id)'}))
but it gets all products so page load time increase.
I would like to use queryset=Product.objects.none().
But at that point when I check the form in my view.py
if request.method == "POST":
formset = product_formset(request.POST)
if formset.is_valid():
I get an error
Select a valid choice. That choice is not one of the available choices
Do you have any suggestion ? Thanks
Forms.py
class OrderProductsForm(forms.ModelForm):
class Meta:
model = OrderProducts
fields = ['amount']
def __init__(self, *args, **kwargs):
super(OrderProductsForm, self).__init__(*args, **kwargs)
self.fields['product_category'] = ModelChoiceField(queryset=ProductCategory.objects.all(),empty_label="Ürün Grubunu seciniz",
widget=forms.Select(attrs={"onChange":'myFunction(this.value,this.id)'}))
#self.fields['product'] = ModelChoiceField(queryset=Product.objects.all(),empty_label="Ürün Seciniz", widget=forms.Select(attrs={"onChange":'stokKontrol(this.value,this.id)'}))
self.fields['product'] = ModelChoiceField(queryset=Product.objects.none() ,empty_label="Ürün Seciniz",required=False,widget=forms.Select(attrs={"onChange":'stokKontrol(this.value,this.id)'}))
self.fields['stok'] = forms.CharField(required=False,disabled=True,max_length=5)
I believe you misunderstand the use of objects.none(). See here for a good explanation.
Product.objects.none() - This simply represents an empty object at first and is used in different use cases. When you set this to the queryset, you will be initialising the queryset to nothing.
The error
Select a valid choice. That choice is not one of the available choices.
is expected since this is not of the available choices for the queryset. To speed the increase of the other method, I would suggest looking at other attributes to speed up the filtering time. This is a possible solution to avoid reloading each time via the use of django-cache.

Django form cleaning acting strangely

I am overwriting clean method but when I do it like this:
def clean(self):
if "post_update_password" in self.data:
print(self.cleaned_data)
old_password = self.cleaned_data['old_password']
new_password1 = self.cleaned_data['new_password1']
new_password2 = self.cleaned_data['new_password2']
return super().clean()
It returns this: {'old_password': 'Password,1.', 'new_password1': 'a'}
which means that I am unable to get that new_password2 value.
And when I change my clean method like this:
def clean_new_password2(self):
if "post_update_password" in self.data:
print(self.cleaned_data)
old_password = self.cleaned_data['old_password']
new_password1 = self.cleaned_data['new_password1']
new_password2 = self.cleaned_data['new_password2']
return super().clean()
It magicaly works and returns:
{'old_password': 'Password,1.', 'new_password1': 'PAssssad', 'new_password2': 'a'}
I really don't understand what is happening. I know how to bypass this issue but I am really curious where is the problem.
Thanks for any reply
Writing clean() and clean_fieldname() methods covers two very different needs.
The former lets you validate the value of a given field data, independently from others. The later allows you to validate your form by taking into account several fields value. So it's normal that the outcome of writing your "validation code" in either the first or second method is different.
Have you tried to follow Django documentation about this subject?
As you will see in this doc, calling super.clean() at the end of a field validation method does not make much sense as it will be called anyway during the whole validation process. This method (for example clean_new_password_2()) should be constructed as follows:
def clean_new_password2(self):
old_value = self.cleaned_data['new_password_2']
new_value = ...
return new_value # this is the value you want for this field
From what I understand from your use case, your code should be:
def clean(self):
cleaned_data = super().clean()
... # Do here the validation stuff you want to do with your field values
# and if you changed the values of ones of the field in cleaned_data, do as follows:
return cleaned_data # you can even return whatever you want
# If you changed nothing, you don't need a return statement

Disable choice list in Django admin, only for editing

I want to disable some fields when I am editing an object. I have managed to do this for text fields, but it's been impossible for a dropdown list (choice list).
I am doing this action in the constructor of the form.
class OrderModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(forms.ModelForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
self.fields['description'].widget.attrs['readonly'] = True
self.fields['city_code'].widget.attrs['disabled'] = True
Notice how I made it for both with different keywords, but I can't do it for my customer_id field.
Setting the attribute to disabled or readonly only affects the way the widgets are displayed. It doesn't actually stop somebody submitting a post request that changes those fields.
It might be a better approach to override get_readonly_fields for your model.
class OrderModelAdmin(admin.Model
def get_readonly_fields(self, request, obj=None):
if self.obj.pk:
return ['description', 'city_code', 'customer']
else:
return []
The answer of #Alasdair is better than this one (because this one doesn't prevent a submission). But I post it, just in case someone wants the equivalent to 'readonly' for ModelChoiceField.
self.fields['customer_id'].widget.widget.attrs['disabled'] = 'disabled'
Notice, that for a ChoiceField is enought something like this:
self.fields['city_code'].widget.attrs['disabled'] = True

What is the difference between a Model field type and a similar validator in Django?

What is the difference between, say, a EmailField and a validator_email? And is it a bad idea to use both?
Or for those who perfer code
import django.db import models
email = models.EmailField()
vs
import django.db import models
email = models.CharField( max_length=75, validators = validate_email )
From the doc it seems like you could also use validators inside forms but if you already specify a validation restriction inside models.py, then you don't need specify again in the forms, right? So it seems better to me to take care of all of the restriction inside models.py.
I suppose the difference is very little, but then you would be violating the DRY principal, which you probably shouldn't do, unless you have a good reason to do it.
If you go to the code base:
#django.db.fields.__init__.py
class EmailField(CharField):
default_validators = [validators.validate_email]
description = _("E-mail address")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs)
def formfield(self, **kwargs):
# As with CharField, this will cause email validation to be performed
# twice.
defaults = {
'form_class': forms.EmailField,
}
defaults.update(kwargs)
return super(EmailField, self).formfield(**defaults)
As you can see, the model inherits from Charfield, so you lose nothing by using emailfield, where appropriate. Furthermore, the default validator is validate_email. Additionally you get the description variable already defined for you. Lastly, on the backend it is already setting max_length for you at '75'. You could of course override this easily enough by defining a max_length in the same way you would when creating a CharField.
You can see formfields() is returning forms.EmailField from django.forms.
Looking at that, you can see:
#django.forms.fields.py
class EmailField(CharField):
default_error_messages = {
'invalid': _(u'Enter a valid e-mail address.'),
}
default_validators = [validators.validate_email]
def clean(self, value):
value = self.to_python(value).strip()
return super(EmailField, self).clean(value)
However, you would lose any default values that using the EmailField might provide, such as the "correct" error message and the custom clean() method.
In the end, while it looks small, actually a good bit of work has already been done for you. So, in general, you shouldn't violate the DRY principal unless you have a good reason to do so.
Edit:
Regarding the second question, you want the form to validate against whatever criteria you are concerned about, so when you call form.is_valid() it returns True / False when it should and generates the appropriate failure message. Otherwise, is_valid() would validate True, and when you model goes to save, it would fail silently, which would be very hard to track down.

Django update instead of insert a new record

I having a weird issue with Django 1.3. When I try to add a new Activity using the ActivityForm, the Activity is often updated even if I explicitly create a new Activity object with not Id.
In addition, when the init of my ActivityForm class has an explicit trip_id parameter, the UI displays "Select a valid choice. That choice is not one of the available choices." for the field location (even though the selection shown is valid). However, if I make this an optional parameter and I pop it from kwargs, I don't see this issue.
Can someone please take a look at the code below and see what I am doing wrong?
forms.py
class DestinationMultipleChoiceField(ModelMultipleChoiceField):
def label_from_instance(self, obj):
return obj.city_name
class ActivityForm(forms.Form):
description = forms.CharField(max_length=100, required=True)
location = DestinationChoiceField(queryset = Visit.objects.none(), empty_label=None, required=True)
def __init__(self, trip_id = None, *args, **kwargs):
super(ActivityForm, self).__init__(*args, **kwargs)
if trip_id is not None:
self.fields['location'].queryset = Visit.objects.filter(trip=trip_id).all().select_related('destination')
# A new Activity() is created if nothing is provided
def save(self, activity = Activity()):
if not self.is_valid():
raise forms.ValidationError('ActivityForm was not validated first before trying to call save().')
activity.visit = self.cleaned_data['location']
activity.description = self.cleaned_data['description']
activity.added_by = User.objects.get(pk=1)
activity.save()
views.py
def add_activity(request, trip_id = None, template_name = 'edit-form.html'):
if request.POST:
form = ActivityForm(trip_id, request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('views.list_trip'))
else:
form = ActivityForm(trip_id)
return render_to_response(template_name, {
'page_title': 'Add',
'form': form,
}, context_instance=RequestContext(request))
Look at this line:
def save(self, activity = Activity()):
In Python, default parameters are evaluated once; thus, when the save method is called the first time, a new Activity will be created, but subsequent calls will use that Activity.
Change save to something like this:
def save(self, activity=None):
if activity is None:
activity = Activity()
# Rest of the method
Then, a new Activity will be created on every call (if one is not supplied as an argument).
Model instances are mutable. As such they should never be used as default arguments to a method, as you do in save. There's a reason why you've never seen that in any documentation or examples.
As noted in very many other questions on this site, default arguments are evaluated on definition, not execution. So each call to save with no activity argument will use the same Activity that was originally defined.
However, I don't understand why you're not using a ModelForm, which does most of this for you in any case.

Categories