How do I dynamically alter field labels in forms in Django
In the below code, the labels word in the def clean_passport function is 'greyed out', saying 'labels' not accessed
Also, 'document_type' is not submitted in the form, it is not included as a form field, but is in the model, and it is assigned a value in the view.... how do I access it, if I need to do customised form validation based on the value of document_type???
Or should I put the logic in the model, and change the field characteristics there? Again, how would I access the value of document_type of a record / object in the model? Can you do that, dynamically change field attributes on a database?
UPDATE
I think I may need to create 4 separate forms as a way around this, unless anyone has a more generalisable / flexible solution
FORM
class LegalDocumentUpdateForm(forms.ModelForm):
# FORM META PARAMETERS
class Meta:
model = Legal_Document
fields = ('name_on_document', 'number_on_document', 'issuing_country', 'issuance_date', 'expiry_date', 'document_location')
labels = {
'name_on_document': _("Name on Document*"),
'number_on_document': _("Document Number"),
'issuing_country': _("Issuing Country"),
'issuance_date': _("Issuance Date"),
'expiry_date': _("Expiry Date"),
'document_location': _("Document Location*")
}
# SANITIZATION & VALIDATION CHECKS
def clean(self):
document_type = self.cleaned_data['document_type']
# document_type is not submitted in the form, but is in the model, and preset in the view.... how do I access it, if I need to do form validation depending on the value of document_type???
if document_type == 'Passport':
number_on_document = self.cleaned_data['number_on_document']
issuing_country = self.cleaned_data['issuing_country']
expiry_date = self.cleaned_data['expiry_date']
if number_on_document == None:
raise forms.ValidationError(_("Please enter the Passport Number."))
if issuing_country == None:
raise forms.ValidationError(_("Please enter the Passport's Issuing Country."))
if expiry_date == None:
raise forms.ValidationError(_("Please enter the Passport's Expiry Date."))
labels = {
'name_on_document': _("Passport Full Name*"),
'number_on_document': _("Passport Number*"),
'issuing_country': _("Issuing Country*"),
'expiry_date': _("Passport Expiry Date*"),
}
MODEL
# LEGAL DOCUMENT MODEL
class Legal_Document(models.Model):
class LegalDocumentTypes(models.TextChoices):
PASSPORT = 'Passport', _('Passport')
BIRTHCERT = 'Birth Certificate', _('Birth or Adoption Certificate')
CERTOFREG = 'Certificate of Registration', _('Certificate of Registration or Naturalisation')
NIPROOF = 'Proof of N.I.', _('Proof of N.I. Document')
related_user = models.OneToOneField(User, on_delete=models.CASCADE, null=False, default='')
document_id = models.BigAutoField(verbose_name='Document ID', primary_key=True, serialize=False, auto_created=True)
document_type = models.CharField(verbose_name='Document Type', max_length=27, choices=LegalDocumentTypes.choices, blank=False, null=False, default=LegalDocumentTypes.PASSPORT)
name_on_document = models.CharField(verbose_name='Name on Document', max_length=100, blank=False, null=False, default='')
number_on_document = models.CharField(verbose_name='Document Number', max_length=20, blank=True, null=False, default='')
issuing_country = CountryField(verbose_name='Issuing Country', max_length=100, blank=True, null=False, default='')
issuance_date = models.DateField(verbose_name='Issuance Date', blank=True, null=True)
expiry_date = models.DateField(verbose_name='Expiry Date', blank=True, null=True)
document_location = models.CharField(verbose_name='Document Location', max_length=30, blank=False, null=False, default='')
Each field has a label attribute which you can set.
E.g.
self.fields['name_on_document'].label = 'Whatever'
Maybe you can use that in the clean method. But I don't see the point as it won't be displayed unless there is an error in the form.
Related
I am trying to access the purchaser field in my ManytoMany field. I used through= to add some extra fields. However, it seems I am only able to access the event object, not the extra fields. Can someone explain to me why?
for selected_order in Order.objects.all():
contact_exists = Contact.objects.filter(
email=selected_order.email,
event_related_fields=selected_order.event,
)
if contact_exists:
contact = contact_exists.first()
for x in contact.event_related_fields.all():
print(x.purchaser)
models.py
class Contact(TimeStampedModel):
consent = models.BooleanField(verbose_name=_("Consent"))
email = models.EmailField(verbose_name=_("Your email"))
first_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("First name"),
null=True,
blank=True,
)
last_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("Last name"),
null=True,
blank=True,
)
events = models.ManyToManyField(Event, related_name='contacts')
event_related_fields = models.ManyToManyField(
Event, related_name='event_related_fields', through='EventRelatedFields'
)
organizer = models.ForeignKey(
Organizer, on_delete=models.PROTECT, related_name='contacts'
) # PROTECT = don't allow to delete the organizer if contact exists
class Meta:
verbose_name = _("Contact")
verbose_name_plural = _("Contacts")
ordering = ('created',)
unique_together = ('email', 'organizer')
def __repr__(self):
return "{}: {}".format(self.__class__.__name__, self)
def __str__(self):
return self.email
class EventRelatedFields(TimeStampedModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
lead = models.BooleanField(
verbose_name='Lead', default=False
) # Contact who 'Signed Up'
attendee = models.BooleanField(
verbose_name='Attendee', default=False
) # Contact assigned to ticket
purchaser = models.BooleanField(
verbose_name='Purchaser', default=False
) # Contact made the order
class Meta:
unique_together = [['event', 'contact']]
You are defining two many-to-many relationships for some reason, and you've called one of them event_related_fields, with the same related name. As a result you are confusing that with the through table. But since the through table is actually calledEventRelatedFields, you would access its related objects as eventrelatedfields_set.
You should only have one m2m, events:
events = models.ManyToManyField(Event, through='EventRelatedFields', related_name='contacts')
and your class EventRelatedFields should itself set related names:
event = models.ForeignKey(Event, related_name='event_related_fields', on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, related_name='event_related_fields, on_delete=models.CASCADE)
Now you can do:
for x in contact.event_related_fields.all():
print(x.purchaser)
I expect to receive a var contact_exists that I can use to update some fields. However, the following query always gives me back django.core.exceptions.FieldError: Related Field got invalid lookup: event
Do you have any idea why event_related_fields__event doesn't work the way I expected?
for selected_order in Order.objects.all():
contact_exists = Contact.objects.filter(
event_related_fields__event=selected_order.event,
)
Here my models.py:
class Contact(TimeStampedModel):
consent = models.BooleanField(verbose_name=_("Consent"))
email = models.EmailField(verbose_name=_("Your email"))
first_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("First name"),
null=True,
blank=True,
)
last_name = models.CharField(
max_length=100, # TODO Length must be same as for billing model
verbose_name=_("Last name"),
null=True,
blank=True,
)
events = models.ManyToManyField(Event, related_name='contacts')
event_related_fields = models.ManyToManyField(
Event, related_name='event_related_fields', through='EventRelatedFields'
)
organizer = models.ForeignKey(
Organizer, on_delete=models.PROTECT, related_name='contacts'
) # PROTECT = don't allow to delete the organizer if contact exists
class Meta:
verbose_name = _("Contact")
verbose_name_plural = _("Contacts")
ordering = ('created',)
unique_together = ('email', 'organizer')
def __repr__(self):
return "{}: {}".format(self.__class__.__name__, self)
def __str__(self):
return self.email
class EventRelatedFields(TimeStampedModel):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
lead = models.BooleanField(
verbose_name='Lead', default=False
) # Contact who 'Signed Up'
attendee = models.BooleanField(
verbose_name='Attendee', default=False
) # Contact assigned to ticket
purchaser = models.BooleanField(
verbose_name='Purchaser', default=False
) # Contact made the order
class Meta:
unique_together = [['event', 'contact']]
You don't need the __event lookup, try using:
for selected_order in Order.objects.all():
contact_exists = Contact.objects.filter(
event_related_fields=selected_order.event,
)
The lookup part should contain field names of Event model.
Scenario:
I instantiate a ModelForm and pass it to a template which displays the form. When POST is submitted, code tries to search the database by any of the given inputs. I dont require all inputs to be entered as in the Model. I just need one (or more, if user desires to do an AND search) to be entered.
Question: How can I make any of the ModelForm fields optional, where in the Model, the field isnt optional. The field isnt optional in the Model because I have another ModelForm based on the same Model, where user is required to enter all his details.
My model:
class customer(models.Model):
# Need autoincrement, unique and primary
cstid = models.AutoField(primary_key=True, unique=True)
name = models.CharField(max_length=35)
age=models.IntegerField()
gender_choices = (('male', 'Male'),
('female', 'Female'))
gender = models.CharField(
choices=gender_choices, max_length=10, default='male')
maritalstatus_choices = ( ('married', 'Married'),
('unmarried', 'Unmarried'))
maritalstatus = models.CharField(
choices=maritalstatus_choices, max_length=10, default='Unmarried')
mobile = models.CharField(max_length=15, default='')
alternate = models.CharField(max_length=15, default='')
email = models.CharField(max_length=50, default='', blank=True)
address = models.CharField(max_length=80, default='', blank=True)
city = models.CharField(max_length=25, default='', blank=True)
occupation = models.CharField(max_length=25, default='', blank=True)
bloodgroup_choices = (('apos', 'A+'),
('aneg', 'A-'),
('bpos', 'B+'),
('bneg', 'B-'),
('opos', 'O+'),
('oneg', 'O-'),
('abpos', 'AB+'),
('abneg', 'AB-'),
('unspecified', '-')
)
bloodgroup = models.CharField(choices=bloodgroup_choices, max_length=3, default='-', blank=True)
class Meta:
unique_together = ["name", "mobile", "age"]
def __str__(self):
return self.name
My form:
class CheckinPatientMetaForm(ModelForm):
class Meta:
model = customer
exclude = [
'gender',
'maritalstatus',
'occupation',
'bloodgroup'
]
views.py:
def checkin_patient(request):
results = ''
if request.method == 'POST':
form = CheckinPatientMetaForm(request.POST)
print("POST data",request.POST)
else:
form = CheckinPatientMetaForm()
return render(request, 'clinic/checkin.html', {'rnd_num': randomnumber(), 'form': form, 'SearchResults': results})
As #bdbd mentioned in comments, you can do it by specifying by required=False.
For example, if you want to age field to be optional, add it explicitly as
from django import forms
class CheckinPatientMetaForm(ModelForm):
age = forms.IntegerField(required=False)
class Meta:
model = customer
exclude = [
'gender',
'maritalstatus',
'occupation',
'bloodgroup'
]
I want to create a ModelForm which gonna show some specific field of ControlInstruction if device_type of Device is equals DC. Otherwise show all fields.
Suppose,
if device type == 'DC':
show these filed in form-> on_off_flag, speed_flag, direction_flag
else:
show all
How can I do that?
class Device(models.Model):
DEVICE_TYPES = (
('AC', 'AC MOTOR'),
('DC', 'DC MOTOR'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
device_id = models.CharField(max_length=64, unique=True, blank=False)
device_name = models.CharField(max_length=100, blank=False)
device_model = models.CharField(max_length=10)
device_type = models.CharField(max_length=2, choices=DEVICE_TYPES, blank=False)
location = models.CharField(max_length=150)
def __str__(self):
return self.device_name
class ControlInstruction(models.Model):
DIRECTION_CHOICES = (
('FW', 'Forward'),
('BW', 'Backward'),
)
# OneToOneField is is similar to a ForeignKey with unique=True, but the “reverse”
# side of the relation will directly return a single object.
device = models.OneToOneField(Device, on_delete=models.CASCADE, primary_key=True)
on_off_flag = models.BooleanField(default=False)
voltage_flag = models.FloatField(max_length=20, default=0)
current_flag = models.FloatField(max_length=20, default=0)
speed_flag = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(100)])
direction_flag = models.CharField(max_length=2, choices=DIRECTION_CHOICES, default='FW')
frequency_flag = models.IntegerField(default=0)
I would recommend creating two forms, one including only the fields for a DC device, and one form with all of the fields. Then in your view, choose which form to use based on the device_type.
class DeviceForm(forms.ModelForm):
class Meta:
model = Device
fields = "__all__"
def __init__(self,*args,**kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
if self.instance.device_type != "DC":
del self.fields["on_off_flag"]
del self.fields["speed_flag"]
del self.fields["direction_flag"]
But I dont recommended since you will find that this approach is very limited
I am trying to validate a form using django forms. I have the following model:
class Session(models.Model):
# id = AutoField(primary_key=True) added automatically.
sport = models.ForeignKey('Sport', unique=False, blank=False, null=False, to_field='sport', on_delete=models.CASCADE, )
hostplayer = models.ForeignKey(User, unique=False, blank=False, null=False, on_delete=models.CASCADE, related_name='member_host', )
guestplayer = models.ForeignKey(User, unique=False, blank=True, null=True, on_delete=models.CASCADE, related_name='member_guest', )
date = models.DateField(blank=False, null=False, )
time = models.TimeField(blank=False, null=False, )
city = models.ForeignKey('City', unique=False, blank=False, null=False, to_field='city', on_delete=models.CASCADE, )
location = models.CharField(max_length=64, unique=False, blank=False, null=False, )
price = models.FloatField(unique=False, blank=False, null=False, default=0, )
details = models.TextField(unique=False, blank=True, null=True, )
def __unicode__(self):
return unicode(self.id)
and the following form:
class CreateSessionForm(forms.Form):
sport = forms.CharField()
date = forms.DateTimeField()
time = forms.TimeField()
city = forms.CharField()
location = forms.CharField()
price = forms.FloatField(required=False, initial=0, )
details = forms.CharField(required=False, )
def clean_price(self):
prc = self.cleaned_data['price']
if prc is None:
return 0
return prc
There is some frontend validation but I want to use the is_valid() functionality as well. I have the following piece of code in views.py:
if request.method == "POST":
form = CreateSessionForm(data = request.POST)
if form.is_valid():
# Create session object.
session = Session.objects.create(sport=Sport.objects.get(sport = form['sport'].data), hostplayer = request.user, guestplayer = None,
date = form['date'].data, time = form['time'].data, city = City.objects.get(city=form['city'].data),
>>> location = form['location'].data, price = form['price'].data, details = form['details'].data)
session.save()
# Return to the session page if successful.
return HttpResponseRedirect('/session/' + str(session.id) + '/')
Everything is working fine, except when the price I receive is ''. I want to allow this and default to 0. However, I receive the following error on the line marked with >>>:
could not convert string to float
When debugging that line, it appears that the value of price in form does not get updated (it appears to be '').
How could I change the value of price in the form to 0 when '' is passed? What would be the best way to go about doing this?
I have been searching for a way to fix this for 3 hours now without success. This is the first time I am using forms in django so I may very well be missing something completely obvious.
Any help will be greatly appreciated.
Fetch the values from form.cleaned_data, instead of using form['fieldname'].data
For example:
date = form.cleaned_data['date']
See the docs on processing the data from a form for further details.
There might be more than one problem, but the most obvious one that stands out is this:
price = modes.FloatField(..., blank=False, ...)
in your Session model.
Set blank=True to allow the form field to be blank. The ORM will then use your default=0 when saving if the form field was blank.