I am using model form and I'm trying to bypass validation of one particular field.
I have ascertained that I need use the clean() method to bypass the field. however when im printing out the cleaned data the assigned_subnets field is not in the dictionary, it is missing
actions:
I create the assigned subnet field manually in forms.py. Then using jquery I alter that form field and add more select options to it post all the options. The additional values posted were not part of the original field choices, hence the error.
output from print:
{'site_data': None, 'switches': None, 'hostname': 'STR--RTR-01', 'template': <ConfigTemplates: STR-RTR-01>, 'model': <DeviceModel: Cisco - 4431>, 'install_date': datetime.date(2016, 5, 26), 'ospf_area': None, 'snmp_data': <SNMPData: XXXX>, 'available_subnets': <QuerySet []>}
forms.py
class DeviceForm(forms.ModelForm):
class Meta:
model = DeviceData
fields = ['site_data', 'switches', 'hostname', 'template', 'model', 'install_date','ospf_area','snmp_data']
def clean(self):
super(DeviceForm, self).clean()
print(self.cleaned_data)
if self.cleaned_data.get('assigned_subnets') in self._errors:
del self._errors['assigned_subnets']
return self.cleaned_data
def __init__(self, *args, **kwargs):
site_id = kwargs.pop('site_id', None)
device_id = kwargs.pop('device_id', None)
self.is_add = kwargs.pop("is_add", False)
super(DeviceForm, self).__init__(*args, **kwargs)
devicesubnet = Subnets.objects.filter(devicesubnets__device_id=device_id)
sitesubnet = Subnets.objects.filter(sitesubnets__site_id=site_id)
common_subnets = list(set(devicesubnet) & set(sitesubnet))
subnet_id_list = []
for s in common_subnets: subnet_id_list.append(s.id)
available_subnet_data = sitesubnet.exclude(id__in=subnet_id_list)
assigned_choices = []
devicesubnet_data = DeviceSubnets.objects.filter(device_id=device_id)
for choice in devicesubnet_data:
assigned_choices.append((choice.subnet.id,choice.subnet.subnet))
self.fields['available_subnets'] = forms.ModelMultipleChoiceField(
label='Available Subnets',
queryset=available_subnet_data,
widget = forms.SelectMultiple(
attrs = {'class': 'form-control', 'size' : '15'}
)
)
self.fields['assigned_subnets'] = forms.MultipleChoiceField(
label='Assigned Subnets',
choices=assigned_choices,
widget = forms.SelectMultiple(
attrs = {'class': 'form-control', 'size' : '15'}
)
)
self.fields['available_subnets'].required = False
self.fields['assigned_subnets'].required = False
self.helper = FormHelper(self)
self.helper.form_id = 'device_form'
self.helper.form_method = 'POST'
...
views.py
class EditDevice(UpdateView):
model = DeviceData
form_class = DeviceForm
template_name = "config/device_form.html"
#method_decorator(user_passes_test(lambda u: u.has_perm('config.edit_device')))
def dispatch(self, *args, **kwargs):
self.site_id = self.kwargs['site_id']
self.site = get_object_or_404(SiteData, pk=self.site_id)
return super(EditDevice, self).dispatch(*args, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("config:device_details", args=(self.site_id,))
def form_valid(self, form):
form.instance.site_data = self.site
assigned_subnets = form.cleaned_data['assigned_subnets']
print(assigned_subnets)
return super(EditDevice, self).form_valid(form)
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs()
kwargs['site_id'] = self.site_id
kwargs['device_id'] = self.object.pk
return kwargs
...
EDIT:
what im trying to do is like in the image below. I have a list of available subnets and a list of chosen (assigned) subnets.
I don think this form widget exists outside of Django admin? so Ive created the two fields manually and used jquery to move a subnet from available to assigned.
then I get the assigned subnets and update the DB. however I get the errors when I alter the assigned field
Related
I have a working formset.
My goal is to define a field choices from other model, based on pk sended on url. It's almost working, but init method is executed twyce and cleans kwargs.
Model:
class Delito(models.Model):
numero = models.ForeignKey(Expediente, on_delete=models.CASCADE, blank = False)
delito = models.ForeignKey(CatalogoDelitos, on_delete=models.CASCADE, blank = False)
imputado = models.ForeignKey(Imputado, on_delete=models.CASCADE, blank = False)
categoria = models.CharField('Categoria', max_length = 20, blank = False, choices = CATDEL_CHOICES)
My URL:
path('crear_delito/<int:pk>', login_required(CrearDelito.as_view()), name ='crear_delito'),
Forms.py:
class CrearDelitoForm(forms.ModelForm):
class Meta:
model = Delito
exclude = ()
def __init__(self, numero_pk = None, *args, **kwargs):
super(CrearDelitoForm, self).__init__(*args, **kwargs)
self.fields["imputado"].queryset = Imputado.objects.filter(numero_id = numero_pk)
DelitoFormset = inlineformset_factory(
Expediente,
Delito,
form=CrearDelitoForm,
extra=1,
can_delete=True,
fields=('imputado', 'delito', 'categoria'),
}
)
Views.py:
class CrearDelito(CreateView):
model = Delito
form_class = CrearDelitoForm
template_name = 'crear_delito.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['formset'] = DelitoFormset()
context['expedientes'] = Expediente.objects.filter(id = self.kwargs['pk'])
return context
def get_form_kwargs(self, **kwargs):
kwargs['numero_pk'] = self.kwargs['pk']
return kwargs
If I print queryset, it works at first time, but is passed twice cleaning "numero_pk" value:
System check identified no issues (0 silenced).
June 08, 2020 - 11:33:57
Django version 2.2.12, using settings 'red.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
<QuerySet [<Imputado: Martín Ric>, <Imputado: Marcos Gomez>]>
<QuerySet []>
If I put the value as string it works fine, eg.:
def __init__(self, numero_pk = None, *args, **kwargs):
super(CrearDelitoForm, self).__init__(*args, **kwargs)
self.fields["imputado"].queryset = Imputado.objects.filter(numero_id = '6')
enter image description here
I publish the solution given in other question, for :
class CrearDelito(CreateView):
model = Delito
form_class = CrearDelitoForm
template_name = 'crear_delito.html'
def get_context_data(self,**kwargs):
context = super().get_context_data(**kwargs)
context['formset'] = DelitoFormset()
context['expedientes'] = Expediente.objects.filter(id = self.kwargs['pk'])
return context
def get_form_kwargs(self, **kwargs):
kwargs['numero_pk'] = self.kwargs['pk']
return kwargs here
I have a question for you. I have the following Model:
class Centro_di_costo(models.Model):
centro_di_costo = models.CharField('Centro di costo', max_length=30)
def __str__(self):
return self.centro_di_costo
class AltriCosti(models.Model):
STATUS_CHOICES= [
('VARIABILE', 'VARIABILE'),
('FISSO', 'FISSO'),
]
centro_di_costo = models.ForeignKey(Centro_di_costo)
sub_centro_di_costo = models.CharField('Categoria', max_length=30)
status = models.CharField(choices=STATUS_CHOICES)
price=models.DecimalField()
quantity=models.IntegerField()
I use it in a lot of view, but in one of them I wanna set the value without passing from the POST request.
So I have tried to set the ModelForm in the following manner:
class ModCollaboratori(forms.ModelForm):
class Meta:
model = AltriCosti
fields = "__all__"
def __init__(self, *args, **kwargs):
super(ModCollaboratori, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_show_labels = False
self.fields['centro_di_costo'].value= "Servizi di Produzione"
self.fields['sub_centro_di_costo'].value = "Collaboratori esterni"
self.fields['status'].value = "VARIABILE"
But It does not work. How could I fix the code to work?
You can exclude fields from your form:
class ModCollaboratori(forms.ModelForm):
class Meta:
model = AltriCosti
exclude = ['centro_di_costo', 'sub_centro_di_costo', 'status']
Then in your view you can "inject" value for these fields:
def some_view(request):
if request.method == 'POST':
form = ModCollaboratori(request.POST, request.FILES)
if form.is_valid():
form.instance.sub_centro_di_costo = 'Collaboratori esterni'
form.instance.status = 'VARIABILE'
form.instance.centro_di_costo = Centro_di_costo.objects.get_or_create(
centro_di_costo='Servizi di Produzione'
)[0]
form.save()
return redirect('name-of-some-view')
else:
form = ModCollaboratori()
return render(request, 'some_template.html', {'form': form})
for your code
self.fields['status'].value = "VARIABLE"
to make it work change to
self.instance.status = "VARIABLE"
Result:
Status: VARIABLE
basically ModelForm.__init__() will populate instance values into form.
but if we add extra field to this form, we will need to populate it by ourself in kwargs["initial"],
because this field not include in the model.
class SomeForm(forms.ModelForm):
custom_field = forms.CharField()
def __init__(self, *args, **kwargs):
kwargs["initial"]["custom_field"] = "xxxxx"
super().__init__(*args, **kwargs)
I am using a django (3.0) ModelMultipleChoice field for a form. I am trying to modify the queryset to make some restrictions on it.
here is the views :
def nouvelle_tache(request,id_livrable):
livrable=Livrable.objects.get(pk=id_livrable)
projet = livrable.projet
if request.method == "POST":
form = NouvelleTache(request.POST,projet=projet)
tache = form.save(commit=False)
tache.livrable = livrable
tache.id_tache = livrable.id_derniere_tache() + Decimal(0.01)
tache.save()
form.save_m2m()
etat = Temps_etat_tache(etat=form.cleaned_data['etat_initial'],tache=tache)
etat.save()
return redirect('tache',tache.pk)
else:
form = NouvelleTache(projet=projet)
return render(request, 'application_gestion_projets_AMVALOR/nouvelle_tache.html', locals())
And the forms :
class NouvelleTache(forms.ModelForm):
def __init__(self, *args, **kwargs):
projet = kwargs.pop('projet', None)
queryset = Utilisateur.objects.all()
for utilisateur in projet.utilisateurs:
queryset = queryset.exclude(pk=utilisateur.pk)
self.fields['ressources'].queryset = queryset
super(NouvelleTache, self).__init__(*args, **kwargs)
ressources= forms.ModelMultipleChoiceField(queryset=Utilisateur.objects.all() ,widget =forms.CheckboxSelectMultiple )
etat_initial = forms.ModelChoiceField(queryset=Etat_tache.objects.none())
class Meta:
model = Tache
fields = ['libelle']
I have the followig error : 'NouvelleTache' object has no attribute 'fields'
I don't understand why because many other users seems to have similar code and it works.
Any help would be appreciate.
super(NouvelleTache, self).__init__(*args, **kwargs)
needs to be executed first, as the fields are set in the super class:
def __init__(self, *args, **kwargs):
projet = kwargs.pop('projet', None)
queryset = Utilisateur.objects.all()
for utilisateur in projet.utilisateurs:
queryset = queryset.exclude(pk=utilisateur.pk)
super(NouvelleTache, self).__init__(*args, **kwargs)
self.fields['ressources'].queryset = queryset
I've been scratching my head over this for the last little while. I have been able to change the modelfield's field queryset and widget attributes, well somewhat!
class InvoiceItemForm(ModelForm):
UOM = forms.ChoiceField (choices = site_defaults.UOM)
class meta:
model = InvoiceItem
fields = ['name', 'costcode', 'rate', 'quantity',]
labels = {'name': 'Item', 'rate': 'Cost Per Unit', 'quantity': 'Base Quantity'}
widgets = {'UOM': forms.Select(choices = site_defaults.UOM )}
def __init__(self, current_user, current_project, *args, **kwargs):
''' Rendering custom ModelForm '''
super(InvoiceItemForm, self).__init__(*args, **kwargs)
the_title = None
the_instance = kwargs.get('instance', None)
if the_instance:
the_costcode = the_instance.costcode
if the_costcode:
the_title = the_costcode.title
self.fields['costcode'].queryset = CostCode.objects.filter(project = current_project, item = 0)
self.fields['costcode'].widget = forms.TextInput(attrs={'class': 'site-flex-select-large', 'value': the_title})
When this is rendered, the costcode field takes the right instance. Also, the class is shown as site-flex-select-large, but the title is shown as the instance.id and not the_title which is the instance.title (a text field is displayed with value of 192 instead of the title of the invoice item).
Why is Django ignoring some changes and accepting some other changes to the field?
I'm not sure if it is a relevant detail or not, but the modelform is used in an inlineformset:
expenses_forms = self.InvoiceItem_InlineFormSet(instance = the_invoice, prefix='expenses', form_kwargs={'current_user': user, 'current_project': project})
A fields widget is not the place that you should be setting initial values for fields. You should set this in the "initial" kwarg to the form's __init__ method, you can pass it to the call to super. You then can set the costcode widget in the Meta
class InvoiceItemForm(ModelForm):
UOM = forms.ChoiceField (choices = site_defaults.UOM)
class Meta:
model = InvoiceItem
fields = ['name', 'costcode', 'rate', 'quantity',]
labels = {'name': 'Item', 'rate': 'Cost Per Unit', 'quantity': 'Base Quantity'}
widgets = {
'UOM': forms.Select(choices = site_defaults.UOM ),
'costcode': forms.TextInput(attrs={'class': 'site-flex-select-large'})
}
def __init__(self, current_user, current_project, *args, **kwargs):
the_instance = kwargs.get('instance', None)
if the_instance:
the_costcode = the_instance.costcode
if the_costcode:
initial = kwargs.get('initial', {})
initial['costcode'] = the_costcode.title
kwargs['initial'] = initial
super(InvoiceItemForm, self).__init__(*args, **kwargs)
EDIT: like Willem says, the costcode field is a TextInput so it does not make sense to set a queryset attribute on it unless you change it to a select
The value is not taken from the attrs, it is taken from the value of that field. You can set the .initial attribute of the field, like:
def __init__(self, current_user, current_project, *args, **kwargs):
''' Rendering custom ModelForm '''
super(InvoiceItemForm, self).__init__(*args, **kwargs)
the_title = None
the_instance = kwargs.get('instance', None)
if the_instance:
the_costcode = the_instance.costcode
if the_costcode:
the_title = the_costcode.title
self.fields['costcode'].queryset = CostCode.objects.filter(project=current_project, item=0)
self.fields['costcode'].initial = the_title
self.fields['costcode'].widget = forms.TextInput(attrs={'class': 'site-flex-select-large'})
That being said, by using a TextInput, it will, as far as I know, just ignore the queryset, and it will not properly validate the data. I think you better use a Select widget [Django-doc] here, and then use some CSS/JavaScript to make it searchable through text.
I have an edit view for one of my models.
#login_required
def edit(request, id):
''' Edit form '''
if id:
post = get_object_or_404(Post, pk=id)
if post.user != request.user:
return HttpResponseForbidden()
else:
post = Post()
if request.POST:
form = PostForm(request.POST, instance = post)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('posts_manage'))
else:
form = PostForm(instance = post)
return render_to_response('posts/add.html', {'form':form}, context_instance=RequestContext(request))
Everything works fine, all the post information is loaded correctly, but one of the fields, which is a select box, is not being selected with the value obtained from the DB. Other select boxes are selected to the appropriate value.
The field that is not being populated properly in the model definition:
class Post(models.Model):
...
BATHROOM_CHOICES = ((1,'1'),(1.5,'1.5'),(2,'2'),(2.5,'2.5'),(3,'3'),(3.5,'3.5'),(4,'4'), (4.5,'4.5'),(5,'5+'))
bathrooms = models.DecimalField(max_digits = 2,decimal_places = 1,choices = BATHROOM_CHOICES)
Relevant section inside add.html:
{{ form.bathrooms|bootstrap}}
forms.py
class PostForm(ModelForm):
class Meta:
model = Post
exclude = ('available','user',)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(PostForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit'] = False
obj = super(PostForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
The data in the DB is not being matched by a choice in BATHROOM_CHOICES
BATHROOM_CHOICES = ((1,'1'),(1.5,'1.5'),(2,'2'),(2.5,'2.5'),(3,'3'),(3.5,'3.5'),(4,'4'), (4.5,'4.5'),(5,'5+'))
and
models.DecimalField(max_digits = 2,decimal_places = 1,
are contradicting.
Your model definition expects all values will have a decimal place of at least 1, and probably coerces values like whole number from 1 to 1.0 in the DB (depending on adapter implementation).
so then when it looks for a choice matching the value 1 !== 1.0 and so no value is selected.
Possible fix:
BATHROOM_CHOICES = ((1.0,'1'),(1.5,'1.5'),(2.0,'2'),(2.5,'2.5'),(3.0,'3'),(3.5,'3.5'),(4.0,'4'), (4.5,'4.5'),(5.0,'5+'))