Django Different form validation when adding and changing instance - python

My question is that can we perform different form validation on the same form based on different action, such as adding or changing.
Say I have a model which has a field named 'type'. And 'type' is an enum field whose choices are 'Manual' and 'Auto'. When adding a new instance, I don't want the user able to set 'type' to 'Auto'. However, the user would view and modify the instance whose 'type' is 'Auto'.
Maybe I don't need validation to do this. So if there is any other option, I' like to hear.
Thanks in advance.

Given the model:
class Spam(models.Model):
foo_type = models.WhateverFieldType(editable=False, choices=FOO_TYPE_CHOICES)
# ... other fields
Use this form (which excludes the foo_type field (disallows editing that field using the form and also disables validation of the field - it's basically, "here, edit anything on this Spam instance except foo_type"):
class SpamForm(forms.ModelForm):
class Meta:
exclude = ('foo_type',)
Then, create a subclass of UpdateView and one of ListView and update the queryset to include only users who's tye_type is set to Auto.
class SpamUpdateView(UpdateView):
def get_queryset(self):
return super(SpamUpdateView, self).get_queryset().filter(foo_type=FOO_TYPE_AUTO)
class SpamListView(ListView):
def get_queryset(self):
return super(SpamListView, self).get_queryset().filter(foo_type=FOO_TYPE_AUTO)

Related

Django forms.Form reflecting property field constraints

I'm implementing a Django Form witch should contain the field 'fieldA' from modelA:
class ModelA(models.Model):
fieldA = models.CharField(max_length=8)
...
My question is: Is there a way to have Django form, which will automatically handle validation of fieldA (check the max_length)? I know I Could use form.ModelFormclass referring to ModelA, but then the form would reflect all the fields of the ModelA. I would like to use simple forms.Form.
I'm looking for a solution like:
class formX(forms.Form):
fieldA = forms.CharField(**modelA.fieldA.constraints)
fieldB = ... some other fields not related to ModelA ...
.... even more fields
Maybe this question is an XY problem, but let me try...
Direct question: get field constraints from existing model
from django import forms
from django.db import models
class Foo(models.Model):
x = models.CharField(max_length=30)
y = models.IntegerField(null=True)
class FooForm(forms.Form):
foo_x = forms.CharField(max_length=Foo._meta.get_field('x').max_length)
You can access the field directly in two ways:
ModelClass.field_name.field (kind of hack, ModelClass.field_name is a django.db.models.query_utils.DeferredAttribute)
ModelClass._meta.get_field('field_name') (better way, described somewhere in docs)
However, this way you have to a) update form if field constraints are added or b) specify all attributes in advance (max_length, min_length, verbose_name, blank, etc.), making declaration of FooForm.foo_x too verbose.
Alternatives
Fields subset
First of all, if you need a subset of Foo fields, use ModelForm and specify them:
class FooForm(forms.ModelForm):
class Meta:
fields = ('x',)
Now you have a form with only x field.
Add some fields
If you want to add fields to this form (that are not related to other model), do it:
class FooForm(forms.ModelForm):
another_field = forms.IntegerField()
class Meta:
fields = ('x',)
def clean_another_field(self):
data = self.cleaned_data['another_field']
if data != 42:
raise ValidationError('Not an answer!') # i18n should be here
return data
Also you can override clean and save methods for another goals, documentation explains that nicely.
Mix fields from different models
If you need fields of two different models to be present in one form, you don't. You need two separate forms in this case, plus some inter-validation logic outside of this forms maybe (as a view method, for example, or as another class that is not a form). Maybe what you need is inline formset, it doesn't represent two mixed forms, but at least has some inter-model communication.
I found a way how to achieve a validation of form Field reflecting constrains from model Field.
class Foo(models.Model):
x = models.CharField(max_length=30)
y = models.IntegerField(null=True)
class FooForm(forms.Form):
foo_x = forms.CharField(validators=Foo._meta.get_field('x').validators)
Like this, the form will respect the max_length validator of attribute x or any other validator defined.

Django - generic ModelAdmin definition

This is a newbie question.
I have multiple models that I would like to show in admin view. I would like to show all model's fields when viewing a list of records for a model.
I'd like to define a generic ModelAdmin subclass which will be able to list out all fields for a model. But not sure how to pass the class objects to it. admin.py looks like this
class ModelFieldsAdmin(admin.ModelAdmin):
def __init__(self, classObj):
self.classObj = classObj
self.list_display = [field.name for field in classObj._meta.get_fields()]
admin.site.register(Candy, ModelFieldsAdmin)
admin.site.register(Bagel, ModelFieldsAdmin)
How can I pass the classObj (which would be one of the models Candy or Bagel) , and also ensure that the get_list_display() picks up the value from self.list_display ?
This isn't the right approach. You shouldn't override __init__ for an admin class.
Rather, define get_list_display and access self.model:
def get_list_display(request):
return [field.name for field in self.model._meta.get_fields()]

In Django, how can I allow 'null' values in a field programatically from the form?

I am actually programing an application in django and I have the next model:
class A(models.Model):
att1= models.ForeignKey(B, related_name="att1")
att2= models.ForeignKey(C)
...
As you can see, the attributes of the model cannot be 'null'.
I also have a search-form of the model above, and, when I treat it in the correspondent view, I have the next error in this code line:
# If the two forms are valid...
if A_form.is_valid():
Error -> Cannot assign None: "A.att1" does not allow null values.
So that I supose the error occurs because in the model I didnĀ“t define null=True for that attribute.
Well, the problem is that when I create an object referenced to de model A, I want the attributes 'att1' and 'att2' not to be 'null', but in the search form it doesn't matter if the fields are filled. Of course, in the form, I defined that the attributes are not required with required=False:
class Aform(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(Aform, self).__init__(*args, **kwargs)
# Making name required
self.fields['att1'].required = False
self.fields['att2'].required = False
So, my question is: how can I fix this error? Can I attach "null=True" option to the attributes in the form, as well as I could do in the model definition? Is there any way to allow the attributes be null in the search form?
Thank you so much!
Assuming that Aform is not overriding directly forms.ModelForm, you can override is_valid or clean_att1
Doc of clean_att1 here

Django - Model field define unicode

As we can define the __unicode__ representation of a model,
Is there a way to define the same for a model field ? (or is it a bad idea ?)
You can add your own methods. For example, when you use choices for a field, django automatically creates a get_FIELD_display method for the FIELD.
class Something(models.Model):
name = models.CharField(max_length=25)
def get_name_uppercase(self):
return self.name.upper()
then when you have
something = Something.get(id=1)
you can access it via
something.get_name_uppercase()

Django: programatic fields in inlines

Expanding from this question: Can "list_display" in a Django ModelAdmin display attributes of ForeignKey fields?, could it be possible to do something like this:
class MyModelInline(admin.StackedInline):
model = MyModel
extra = 1
fields = ('my_field',)
def my_field(self, obj):
return obj.one_to_one_link.my_field
If something like this were possible, it would solve most of my current Django problems, but the code above does not work: Django (rightly) complains that my_field is not present in the form.
You can do that, but you must also add my_field to your MyModelInline class's readonly_fields attribute.
fields = ('my_field',)
readonly_fields = ('my_field',)
From the docs:
The fields option, unlike list_display, may only contain names of fields on the model or the form specified by form. It may contain callables only if they are listed in readonly_fields.
If you need the field to be editable, you should be able to do that with a custom form but it takes more work to process it.

Categories