Is it possible in django to have a ChoiceField on a formset level rather than an inline form? So for example if I have a formset for phones and each inline form represents a single Phone model, how can I have a ChoiceField that spans all the inline forms? Something like this where I'm choosing a primary phone:
My models:
class Profile(models.Model):
verified = models.BooleanField(default=False)
primary_phone = models.OneToOneField('Phone', related_name='is_primary', null=True, blank=True)
class Phone(models.Model):
profile = models.ForeignKey(Profile, editable=False)
type = models.CharField(choices=PHONE_TYPES, max_length=16)
number = models.CharField(max_length=32)
#property
def is_primary(self):
return profile.primary_phone == self
I can always remove primary_phone and use a BooleanField in Phone to indicate if it's primary or not, but I'm not sure if this going to help my problem.
I'm also looking for a less-hacky more-django-like approach if possible.
There is no way to have django create this for you automatically. In your ModelForm (that's used in the inline) I'd add a boolean field called is_primary. This field will then show up on each inlined Phone instance (as a checkbox).
On the front end sort it out with javascript so that a user can only select one default at a time. On the back end use some custom validation to double check that only one is_default was submitted, and then update the primary_phone as necessary with form logic.
Related
I am using Django Admin, and have a model like this:
class Item(models.Model):
id = models.CharField(max_length=14, primary_key=True)
otherId = models.CharField(max_length=2084, blank=True)
I want id to be required and unique, and I want otherId to be optional on the Admin form, but if otherId is provided, it has to be unique.
The problem I am running into is, whenever I create an instance of Item using the Admin form and I do not provide an otherId, Django tries to save the otherId field as a blank value, but this means the second time I try to save an instance with a blank otherId value it violates the column's unique constraint and fails.
I need Django to check if the otherId field is falsey before saving, and if it is falsey, do not save that empty value along with the model. Is this possible?
You should add unique=True to otherId field.
otherid = models.CharField(max_length=2084, blank=True, null=True, unique=True)
Django ignore unique or not if otherId is blank.
I failed to understand the question very well but i think you need to override the save method of the django model and provide custom logic you stated above.
class Item(models.Model):
id = models.CharField(max_length=14, primary_key=True)
otherId = models.CharField(max_length=2084, blank=True)
def save(self, *args, **kwargs):
# handle you logic here
# check if self.id is empty string and do something about it
super(Item, self).save(*args, **kwargs)
For every model django also auto create a field id for primary key which is auto generated and incremented.
For disabling submission of blank field you must make the null and blank property False. Check the code.
Also note that the id field is automatically added in django so you need not mention that.
class Item(models.Model):
otherId = models.CharField(max_length=2084, blank=False, null=False)
I have a Django model described as follows-
class Building(models.Model):
name = models.CharField(max_length=25,unique=True,blank=False)
country = models.CharField(max_length=20,blank=False)
city = models.CharField(max_length=20,blank=False)
def __str__(self):
return self.name
All of the fields are required as blank is set to False in each of them but still, I am able to save objects for this model by leaving city and country as blank.
blank=True is enforced at the ModelForm layer, as is specified in the documentation on the blank=… parameter [Django-doc]:
Note that this is different than null. null is purely database-related, whereas blank is validation-related. If a field has blank=True, form validation will allow entry of an empty value. If a field has blank=False, the field will be required.
So a ModelForm that uses this model will raise errors when you do not enter a value for name, city, and country. But not the model layer. In fact the model layer does not validate any constraints, unless you call .clean(), or the database rejects the values.
When creating a model in Django like this example:
class Musician(models.Model):
first_name = models.CharField(max_length=50, primary_key=True)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
I noticed some problem (not sure if that's best word) with this approach. There is nothing preventing you from creating something like:
musician = Musician()
musician.save()
effectively having primary_key value equal to None. I would like to force user to set first_name, but frankly speaking I cannot find any simple solution for that.
Is there a way to achieve this?
First of all, don't set first_name as primary key. Just leave the default primary key as the id field. A primary key needs to be unique (a first_name isn't) and should not be something a user enters.
Second, it's true that you cannot enforce a CharField to not be empty at the database level. But you can enforce it at the code level, so that anytime you create a Django Form and validate it, it will raise an error.
In fact, Django does it automatically for you, in your case. By default first_name is a required field, since you didn't set blank=True.
So if you do:
musician = Musician()
musician.full_clean()
this raises a ValidationError.
If you create a form for your model (which is what you need if you want users to create a Musician):
class MusicianForm(ModelForm):
class Meta:
model = Musician
fields = '__all__'
form = MusicianForm(data={})
form.instance.first_name
# ''
form.is_valid()
# False
form.save()
# ValueError: The Musician could not be created because the data didn't validate.
You'll also see that if you register Musician in admin.py for django admin site, you can't leave any of the fields empty. It just won't save.
The site I am building uses modelforms several times. Until now validation of the forms using is_valid has gone smoothly. Now, a new model I've created has decided that the id field (which is generated automatically for model forms) is a required field. I am rendering the fields manually in the template and am not rendering the id field. This has not been a problem with any of my modelforms until now, since it has never been registered as a required field. With this problematic model however, since I am not rendering the field, it gets returned empty and so doesn't pass validation.
I realize that I could solve the problem by rendering the field, but I'd like to understand whats going on here. Why has it decided, seemingly randomly, that the id is required?
Here is the problematic model:
class Item(models.Model):
budgetcatagory=models.ForeignKey(BudgetCatagory, on_delete=models.CASCADE)
name=models.CharField(max_length=30)
enName=models.CharField(max_length=30, default ="")
detail=models.CharField(max_length=30)
layout=models.CharField(max_length=30, default="normal")
unit=models.CharField(max_length=30)
unit_description=models.CharField(max_length=30, default="")
unit_price=models.IntegerField(default=0)
QTY=models.IntegerField(default=0)
param1=models.CharField(max_length=30, blank=True)
param2=models.CharField(max_length=30, blank=True)
param3=models.CharField(max_length=30, blank=True)
param4=models.IntegerField(default=0)
parent=models.CharField(max_length=30, default = "0")
cost_ave=models.IntegerField(default=0, blank=True)
cost_min=models.IntegerField(default=0, blank=True)
cost_max=models.IntegerField(default=0, blank=True)
total_cost=models.IntegerField(default=0)
choiceList=(
('choice1',param1),
('choice2',param2),
)
ItemChoice=models.CharField(
max_length=10,
choices=choiceList,
default='',
)
objects=ItemManager()
def __str__(self):
return self.name
Here is how I am populating the form before sending to template:
else:
#populate
I=Item.objects.filter(budgetcatagory__user_id=U.id)
C=BudgetCatagory.objects.filter(user_id=U.id)
#initiate initial catagories and items for new user
if (not I.exists()) or (not C.exists()):
Item.objects.filter(budgetcatagory__user_id=U.id).delete()
BudgetCatagory.objects.filter(user_id=U.id).delete()
InitiateNewUser(U)
I=Item.objects.filter(budgetcatagory__user_id=U.id)
C=BudgetCatagory.objects.filter(user_id=U.id)
FormsetItem=ItemFormSet(queryset=I)
FormsetCat=CatFormset(queryset=C)
return render(request,'getdata/budgetmachine.html', {'FormsetItem':FormsetItem, 'FormsetCat':FormsetCat })
And here is how I am populating the form from the POST:
if request.method=='POST':
#Save
FormsetItem=ItemFormSet(request.POST,queryset=Item.objects.filter(budgetcatagory__user_id=U.id))
FormsetCat=CatFormset(request.POST)
if FormsetItem.is_valid():
I've been breaking my head against this for days. Any help would be appreciated.
Thanks
EDIT:
Alasdair, following your answer I renderered the entire formset automatically {{ formset }} to ensure that all necessary fields would be rendered. It is now failing validation for an even weirder reason:
{'id': ['Select a valid choice. That choice is not one of the available choices.']}
Obviously I haven't set up my id as a choice field as I haven't set it up at all. it gets generated automatically. I am slowly going insane! Any help would be more than welcome.
The id field is required for model formsets, you have to include it in the template. Perhaps you were using individual model forms before, where it isn't required.
I have models similar to the following:
class Band(models.Model):
name = models.CharField(unique=True)
class Event(models.Model):
name = models.CharField(max_length=50, unique=True)
bands = models.ManyToManyField(Band)
and essentially I want to use the validation capability offered by a ModelForm that already exists for Event, but I do not want to show the default Multi-Select list (for 'bands') on the page, because the potential length of the related models is extremely long.
I have the following form defined:
class AddEventForm(ModelForm):
class Meta:
model = Event
fields = ('name', )
Which does what is expected for the Model, but of course, validation could care less about the 'bands' field. I've got it working enough to add bands correctly, but there's no correct validation, and it will simply drop bad band IDs.
What should I do so that I can ensure that at least one (correct) band ID has been sent along with my form?
For how I'm sending the band-IDs with auto-complete, see this related question: Django ModelForm Validate custom Autocomplete for M2M, instead of ugly Multi-Select
You can override the default fields in a ModelForm.
class AddEventForm(forms.ModelForm):
band = forms.CharField(max_length=50)
def clean_band(self):
bands = Band.objects.filter(name=band,
self.data.get('band', ''))
if not bands:
raise forms.ValidationError('Please specify a valid band name')
self.cleaned_data['band_id'] = bands[0].id
Then you can use your autocomplete widget, or some other widget. You can also use a custom widget, just pass it into the band field definition: band = forms.CharField(widget=...)