How to use model clean method in django and iterate over fields - django

I asked a question and, as I have read a little, I now can better express what I need:
How to do model level custom field validation in django?
I have this model:
class StudentIelts(Model):
SCORE_CHOICES = [(i/2, i/2) for i in range(0, 19)]
student = OneToOneField(Student, on_delete=CASCADE)
has_ielts = BooleanField(default=False,)
ielts_listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_writing = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_speaking = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
and have this model form:
class StudentIeltsForm(ModelForm):
class Meta:
model = StudentIelts
exclude = ('student')
def clean(self):
cleaned_data = super().clean()
has_ielts = cleaned_data.get("has_ielts")
if has_ielts:
msg = "Please enter your score."
for field in self.fields:
if not self.cleaned_data.get(str(field)):
self.add_error(str(field), msg)
else:
for field in self.fields:
self.cleaned_data[str(field)] = None
self.cleaned_data['has_ielts'] = False
return cleaned_data
What I am doing here is that checking if has_ielts is True, then all other fields should be filled. If has_ielts is True and even one field is not filled, I get an error. If has_ielts is False, an object with has_ielts=False and all other fields Null should be saved. I now want to do it on the model level:
class StudentIelts(Model):
SCORE_CHOICES = [(i/2, i/2) for i in range(0, 19)]
student = OneToOneField(Student, on_delete=CASCADE)
has_ielts = BooleanField(default=False,)
ielts_listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_writing = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
ielts_speaking = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
def clean(self):
# I do not know what to write in here
and have this model form:
class StudentIeltsForm(ModelForm):
class Meta:
model = StudentIelts
exclude = ('student')
In the clean method of my model I want something with this logic(this is psedue code):
def clean(self):
msg = "Please enter your score."
if self.has_ielts:
my_dic = {}
for f in model_fields:
if f is None:
my_dic.update{str(field_name): msg}
raise ValidationError(my_dic)
How can I do this?
How can I get the same result as my modelform but at the model level?

You need to explicitly declare the fields that should be non-empty, otherwise you're cycling through fields that are not related to your clean method, like id and student. Someone might want to add a field later one that's not mandatory and wonder why it raises a validation error.
class StudentIelts(Model):
# fields
non_empty_fields = ['ielts_reading', ...]
def clean(self):
errors = {}
if self.has_ielts:
for field_name in self.non_empty_fields:
if not getattr(self, field_name):
errors[field_name] = msg
if errors:
raise ValidationError(errors)

Related

Django / Customized ModelChoiceField: how label_from_instance works?

I use ModelChoiceField with initial value just for display (readonly field).
But value displayed is model id and I want to display customized value that will be a concatenation of 3 fields. I've override ____str____ method but it is not applyed.
I try to make customize ModelChoiceFiedl to define label_from_instance that seems to be the way to do what I want but it is not applyed...
models.py
class Utilisateur(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE_CASCADE
uti_ide = models.AutoField(primary_key = True)
# pro_ide = models.ForeignKey(Projet, on_delete = models.CASCADE) # related project
projets = models.ManyToManyField(Projet, through='UtilisateurProjet')
uti_nom = models.CharField("Nom", max_length=20)
uti_pre = models.CharField("Prénom", max_length=20)
uti_mai = models.CharField("Email", max_length=40)
uti_sit = models.CharField("Equipe", max_length=20, null=True, blank=True)
uti_pro = models.CharField("Fonction/profil", max_length=200, null=True, blank=True)
uti_log = models.CharField("Log utilisateur", max_length=20, null=True, blank=True)
uti_dat = models.DateTimeField("Date log",auto_now_add=True, null=True, blank=True)
log = HistoricalRecords()
#classmethod
def options_list(cls,pro_ide):
# projet = Projet.objects.get(pro_ide=pro_ide)
# utilisateurs = Utilisateur.objects.filter(pro_ide=projet.pro_ide)
utilisateurs = Utilisateur.objects.filter(projets__pro_ide=pro_ide)
the_opts_list = [(utilisateur.uti_ide, utilisateur.uti_nom+', '+utilisateur.uti_pre) for utilisateur in utilisateurs]
the_opts_list.insert(0, (None, ''))
return the_opts_list
class Meta:
db_table = 'tbl_uti'
verbose_name_plural = 'Utilisateurs'
ordering = ['uti_ide']
def __str__(self):
return f"{self.uti_nom}, {self.uti_pre} ({self.uti_mai})"
forms.py
class UtilisateurModelChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
print('label')
return obj.uti_nom+', '+obj.uti_pre+' ('+obj.uti_mai+')'
class UtilisateurProjetUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(UtilisateurProjetUpdateForm, self).__init__(*args, **kwargs)
# print('kwargs',self.request)
# print('projet',self.request['projet'])
# print('utilisateur',self.request['utilisateur'])
PROJETS = Projet.objects.all()
UTILISATEURS = Utilisateur.objects.all()
self.fields["pro_ide"] = forms.ModelChoiceField(queryset = PROJETS, label = "Nom projet", widget = forms.HiddenInput(), initial = Projet.objects.get(pro_ide=self.request['projet']))
self.fields["uti_ide"] = UtilisateurModelChoiceField(queryset = UTILISATEURS, label = "Nom, prénom de l'utilisateur", widget = forms.TextInput(attrs={'readonly':'readonly'}), initial = Utilisateur.objects.get(uti_ide=self.request['utilisateur']),) #,to_field_name="uti_nom"
class Meta:
model = UtilisateurProjet
fields = ('pro_ide','uti_ide',)
in forms.py under that last class Meta: you need to add:
field_classes = {
'pro_ide': UtilisateurModelChoiceField,
'uti_ide': UtilisateurModelChoiceField,
}

Saving formset when a compulsory field was not supplied?

I get an error "NOT NULL constraint failed" when I try to save a formset in an update view and the formset has had new forms added. I think the reason is that the database has a required field (journal_entry) that isn't part of the Formset ModelForm. So when the formset is attempted to be saved lineitem_formset.save() I get the error.
How can I add this required field value before saving the formset?
View.py
#login_required
def entries_update(request, pk):
journal_entry = get_object_or_404(JournalEntry, pk=pk)
journal_entry.date = journal_entry.date.strftime('%Y-%m-%d') #Convert date format to be suitable for Datepicker input.
journal_entry_form = JournalEntryForm(instance=journal_entry)
LineItemFormSet = modelformset_factory(LineItem, fields=('ledger','description','project','cr','dr'), extra=2)
line_items = LineItem.objects.filter(journal_entry=journal_entry)
lineitem_formset = LineItemFormSet(queryset=line_items)
if request.method == 'POST':
lineitem_formset = LineItemFormSet(request.POST)
journal_entry_form = JournalEntryForm(request.POST, instance=journal_entry)
if lineitem_formset.is_valid() and journal_entry_form.is_valid:
lineitem_formset.save() <-- ERROR HAPPENS HERE
journal_entry_form.save()
messages.success(request, "Journal entry successfully updated.")
return HttpResponseRedirect(reverse('journal:entries_show_detail', kwargs={'pk': journal_entry.id}) )
Models.py
class JournalEntry(models.Model):
# User needs to be set back to compulsory !!!
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, null=True, blank=True)
date = models.DateField(null=False, blank=False)
TYPE = (
('BP', 'Bank Payment'),
('YE', 'Year End'),
('JE', 'Journal Entry')
)
type = models.CharField(
max_length=2,
choices=TYPE,
blank=True,
default='0'
)
description = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
if self.description:
return self.description
else:
return 'Journal Entry' + str(self.id)
class Meta(object):
ordering = ['id']
verbose_name_plural = 'Journal entries'
class LineItem(models.Model):
journal_entry = models.ForeignKey(JournalEntry, on_delete=models.CASCADE) <--- This is the field that needs to be set.
ledger = models.ForeignKey(Ledger, on_delete=models.PROTECT)
description = models.CharField(max_length=255, null=True, blank=True)
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True)
cr = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
dr = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True)
STATUS = (
('0', 'Not reconciled'),
('1', 'Draft'),
)
status = models.CharField(
max_length=1,
choices=STATUS,
default='0'
)
reconciliation_date = models.DateField(null=True, blank=True)
#def __str__(self):
# return self.description
class Meta(object):
ordering = ['id']
forms.py
class JournalEntryForm(ModelForm):
def clean_date(self):
data = self.cleaned_data['date']
#Check date is not more than 30d future
if data > (datetime.date.today() + datetime.timedelta(30)):
raise ValidationError('Date cannot be more than 30d future')
if data < (datetime.date.today() - datetime.timedelta(90)):
raise ValidationError('Date cannot be more than 90d past')
return data
class Meta:
model = JournalEntry
fields = ['date','description']
widgets = {'date': DateTypeInput()}
class LineItemForm(ModelForm):
class Meta:
model = LineItem
fields = ['ledger','description','project','cr','dr']
# This init disallows empty formsets
def __init__(self, *arg, **kwarg):
super(LineItemForm, self).__init__(*arg, **kwarg)
self.empty_permitted = False
def clean(self):
cr = self.cleaned_data['cr']
dr = self.cleaned_data['dr']
if cr == None and dr == None:
raise ValidationError('You must enter a CR or DR.')
if cr and dr:
raise ValidationError('You must enter a CR or DR, not both.')
Thanks to #Iain Shelvington again. Edited the following lines to use inline_formset and now works:
LineItemFormSet = inlineformset_factory(JournalEntry, LineItem, fields=('ledger','description','project','cr','dr'), extra=2)
and:
lineitem_formset = LineItemFormSet(request.POST, instance=journal_entry)

How to automatically populate fields in a specific form

I'm developing a django app to keep track of orders and products in my laboratory. I have an Order model that I create instances from with a form that fills some of its fields (but not all) and creates an object.
I've also created an UpdateForm to update the blank fields once the order arrives to the lab. This update form has just one field (storage location), but I want this form to automatically set the status of the order to "received" and populate the "received_by" field with the logged user and "received_date" with the dateTime when the form is sent..
While writing this I just thought I could create a different model for Receive and relate it to the Order model via OnetoOne, would that be a proper solution?
How my code looks like:
#models.py-------------------------------------------------------------
class Pedido(models.Model):
nombre = models.CharField(max_length=40, help_text=_('Nombre del producto.'))
referencia = models.CharField(max_length=20, help_text=_('Referencia del fabricante.'))
cpm = models.CharField(max_length=20, default ='A la espera.',help_text=_('Código del CPM asignado a este pedido.'), null = True, blank = True, verbose_name = _('CPM'))
fecha = models.DateTimeField(auto_now_add=True)
unidades = models.IntegerField(default = 1)
usuario = models.ForeignKey(User, on_delete=models.CASCADE, blank = True)
autogestion = models.BooleanField(default = False, verbose_name='Autogestión', help_text = _("Marca esta casilla si vas a procesar tu mismo el pedido."))
usuario_recepcion = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='recepcion')
fecha_recepcion = models.DateTimeField(blank=True, null=True)
ESTADO_PEDIDO = (
('n', _('Pendiente')),
('p', _('Proforma solicitada')),
('c', _('CPM solicitado')),
('v', _('Para validar')),
('r', _('Recibido'))
)
estado = models.CharField(
max_length=1,
choices=ESTADO_PEDIDO,
blank=False,
default='n',
help_text=_('Estado del pedido'),
)
fabricante = models.ForeignKey('Fabricante', null = True, on_delete=models.SET_NULL)
centro_gasto = models.ForeignKey('CentroGasto', null = True, on_delete=models.SET_NULL, verbose_name = _('Centro de Gasto'))
almacen = models.ForeignKey('Almacen', null = True, on_delete=models.SET_NULL, blank = True)
direccion = models.ForeignKey('Direccion', default = 'CIBM', null = True, on_delete=models.SET_NULL, verbose_name = _('Dirección de entrega'))
codigo = models.CharField(max_length=20, blank=True, default=keygen())
#views.py----------------------------------------------------------
class PedidoListView(LoginRequiredMixin, generic.ListView):
model = Pedido
ordering = ['-fecha']
class PedidoDetailView(LoginRequiredMixin, generic.DetailView):
model = Pedido
#login_required
def Guia(request):
return render(request, 'guia.html')
#login_required
def add_pedido(request):
if request.method == "POST":
form = PedidoForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.usuario = request.user
model_instance.save()
return redirect('/')
else:
form = PedidoForm()
return render(request, "nuevo_pedido.html", {'form': form})
class RecepcionUpdate(LoginRequiredMixin, UpdateView):
model = Pedido
fields = ['almacen']
template_name_suffix = '_recepcionar'
#forms.py-----------------------------------------------------------------
class PedidoForm(ModelForm):
def clean_usuario(self):
if not self.cleaned_data['usuario']:
return User()
return self.cleaned_data['user']
class Meta:
model = Pedido
exclude = ['codigo', 'fecha', 'cpm', 'almacen', 'estado', 'usuario']
If you are trying to change predefined class in Django, it's always possible to overwrite it. For example, you could extend the form_valid method:
class RecepcionUpdate(LoginRequiredMixin, generic.UpdateView):
model = Pedido
fields = ['almacen']
template_name_suffix = '_recepcionar'
def form_valid(self, form):
instance = form.save(commit=False)
instance.user = self.request.user
super(RecepcionUpdate, self).save(form)
Note: I have not tested this, it is probable that instead of calling super().save(form) you should return super().form_valid(form). Anyways, I'm sure that with this example you'll be able to find plenty of code snippets matching your Django version.

Forms in Django. How to initialize model field from form?

In models.py:
class Client(AbstractBaseUser):
username = models.CharField(max_length=32, unique=True)
email = models.EmailField('email address', unique=True, db_index=True)
avatar = models.ImageField('avatar', upload_to='avatars')
id = id(object)
class Order(models.Model):
class Meta():
db_table = 'order'
short_desc = models.CharField(max_length=30)
subject = models.ForeignKey(Subject, blank=True)
user_id = models.ForeignKey('Client', to_field='id', related_name='client_id', default='0', blank=True)
performer_id = models.ForeignKey('Client', to_field='id', related_name='performer_id', default='0', blank=True)
worktype = models.ForeignKey(Type, blank=True)
level = models.IntegerField(default=0, blank=True)
readiness = models.BooleanField(default=False, blank=True)
description = models.TextField(max_length=2000, blank=True)
file = models.FileField(upload_to='orderfiles', blank=True)
#maxdate = models.DateField(blank=True)
addate = models.DateField(auto_now=True, blank=True)
price = models.IntegerField(max_length=10, blank=True)
responses = models.IntegerField(blank=True)
In forms.py:
class AddOrderForm(forms.ModelForm):
short_desc = forms.CharField(widget=forms.TextInput,label="Краткое описание(послужит именем)")
subject = forms.ModelChoiceField(queryset=Subject.objects.all(), label="Предмет")
worktype = forms.ModelChoiceField(queryset=Type.objects.all(), label="Тип")
level = forms.IntegerField(widget=forms.TextInput,label="Уровень сложности (от 1 до 5)")
description = forms.CharField(widget=forms.TextInput,label="Полное описание")
#maxdate = forms.DateField(widget=forms.TextInput,label="maxdate")
price = forms.IntegerField(widget=forms.TextInput,label="Ваша цена")
responses = forms.IntegerField(widget=forms.TextInput,label="Кол-во ответов на заказ")
class Meta:
model = Order
fields = ['short_desc', 'level', 'description', 'price', 'responses', 'subject', 'worktype']
In views.py:
def addorder(request, user_id):
"""
Adding Order view
"""
if request.POST:
form = AddOrderForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
return redirect('/')
auth1 = auth.get_user(request).username
return render_to_response('customer.html', { 'form': form,'username' : auth1}, context_instance=RequestContext(request))
I need the field user_id in class Order to be initialized immediately after adding order(). Where should I do it and in which way? I need something like this logic: Client adds an Order through AddOrderForm and then user_id field of just added object of class Order has to be initialized with an object of class Client, whose id equals user_id in parameters of addorder() function.
You can do that using commit=False while saving the form. This is typical way of saving the object using model form which has fewer fields.
def addorder(request, user_id):
"""
Adding Order view
"""
if request.POST:
form = AddOrderForm(request.POST)
if form.is_valid():
order = form.save(commit=false)
order.client_id = Client.objects.get(id=user_id)
order.save()
return redirect('/')
else:
return redirect('/')
auth1 = auth.get_user(request).username
return render_to_response('customer.html',
{ 'form': form,'username' : auth1},
context_instance=RequestContext(request))
Disclaimer: Handle errors e.g. Client.objects.get() may fail. Use appropriate fields to search.

Django: Form save method

I have a form, working off different models and using a through (intermediate) model:
class CourseBooking(BaseModel):
'''Intermediary model linking a person on a course with the related booking'''
course = ForeignKey('ScheduledCourse')
customer = ForeignKey('Customer')
booking = ForeignKey('GroupBooking', blank=True, null=True)
The form is using a basic form instead of Model form, with the fields added in manually:
class CourseBookingForm(Form):
course = ModelChoiceField(queryset=ScheduledCourse.objects.all())
title = CharField(
max_length=255,
widget=Select(choices=TITLE_CHOICES),
required=False
)
gender = CharField(
max_length=255,
widget=Select(choices=GENDER_CHOICES),
required=False
)
first_name = CharField(max_length=255)
surname = CharField( max_length=255)
dob = DateField(required=False)
medical = CharField(required=False, widget = forms.Textarea(attrs={'rows': '4'}))
# miscellaneous notes
notes = CharField(required=False, widget = forms.Textarea(attrs={'rows': '4'}))
email = EmailField(required=False)
phone = CharField(required=False)
address = CharField(
max_length=8188,
widget=Textarea(attrs={'rows':'4', 'cols':'50'}),
required=False)
city = CharField(max_length=255, required=False)
county = CharField(
max_length=255, widget=Select(choices=COUNTY_CHOICES))
postcode = CharField(max_length=255, required=False)
country = CharField(
max_length=255,
widget=Select(choices=COUNTRIES), required=False)
I want to create a save method in the forms.py which will save to the database. What I have at the moment (which is wrong) is and gives the error: IntegrityError: null value in column "customer_id" violates not-null constraint
def save(self):
data = self.cleaned_data
if self.cleaned_data['course']:
crs = self.cleaned_data['course']
course_booking = CourseBooking(course=crs)
course_booking.save()
course = CourseBooking.objects.create(course=data['course'])
course.save()
cust = Customer.objects.create(title=data['title'],
gender=data['gender'],
first_name=data['first_name'],
surname=data['surname'],
dob=data['dob'],
notes=data['notes'],
medical=data['medical'],
content_object=cust,
)
cust.save()
address = Address.objects.create(address=data['address'],
city=data['city'],
county=data['county'],
postcode =data['postcode'],
country=data['country'],
content_object=address,
)
address.save()
email = Email.objects.create(email=data['email'],
content_object=email)
email.save()
phone = Phone.objects.create(number=data['phone'],
content_object=phone)
phone.save()
Just call the code for creation of the course object after creating the customer object.
The issue is, the ForeignKey customer in Course model is required, and you have not set that field while creating the object.
You have a couple of other minor issues which I have fixed in the code. .
def save(self):
data = self.cleaned_data
course_booking = None
if self.cleaned_data['course']:
crs = self.cleaned_data['course']
course_booking = CourseBooking(course=crs)
course_booking.save()
cust = Customer.objects.create(title=data['title'],
gender=data['gender'],
first_name=data['first_name'],
surname=data['surname'],
dob=data['dob'],
notes=data['notes'],
medical=data['medical'],
content_object=cust,
)
#cust.save()
course = CourseBooking.objects.create(course=data['course'], customer = cust)
if course_booking:
course.booking = course_booking
#course.save()
address = Address.objects.create(address=data['address'],
city=data['city'],
county=data['county'],
postcode =data['postcode'],
country=data['country'],
content_object=address,
)
#address.save()
email = Email.objects.create(email=data['email'],
content_object=email)
#email.save()
phone = Phone.objects.create(number=data['phone'],
content_object=phone)
#phone.save()
On another note, I would put this object creation logic in the view, rather than the model_form's save method.