how to overwrite save method in django model form - python

i'm trying to overwrite save method in my forms.py ,i have to prevent from creating duplicated objects , and if the object exists only update some fields
class Item(models.Model):
item = models.ForeignKey(Product,on_delete=models.CASCADE)
quantity = models.IntegerField()
for example if i entered this data before : item = XYZ , quantity = 100 i want to prevent from creating another XYZ item , i want to just update the quantity , for example i'll enter this data item = XYZ , quantity = 200 i try to prevent from creating this duplicate data , i just try to update the quantity previous quantity + new quantity 100 + 200 = 300 i must update the quantity to 300 for that purpose i overwrite save() in my forms.py
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ['item','quantity']
def save(self,*args,**kwargs):
if self.instance.item is None: #i also tried this if not self.instance.item
return super().save(*args,**kwargs)
else:
Item.objects.filter(item__name=self.instance.item).update(
quantity=F('quantity') + self.instance.quantity)
my views.py
def createNewProduct(request):
form = ItemForm()
if request.method == 'POST':
form = ItemForm(request.POST)
if form.is_valid():
form.save()
return render(request,'temp/add_item.html',{'form':form})
but it only update if it exists if not exists it doesn't create any new object , iexpect to create new object if it didn't exists , isn't there any way to achieve it please ? or i didn't something wrong ?

This is How I usually overwrite save method in model form:
def save(self, commit=True):
# your logic or Save your object for example:
obj = Model.objects.create(...)
return obj
Or you can also do this:
def save(self, commit=True):
obj = super().save(commit=False)
# do you logic here for example:
obj.field = something
if commit:
# Saving your obj
obj.save()
return obj

According to the documentation for ModelForm.save():
A subclass of ModelForm can accept an existing model instance as the
keyword argument instance; if this is supplied, save() will update
that instance. If it’s not supplied, save() will create a new instance
of the specified model.
This means that in your createNewProduct view, when handling POST requests, you need to check whether an Item already exists in the database and if so pass it to the Form constructor for editing, otherwise instantiate the ModelForm as per usual to create a new Item. So actually there's no need to override the ModelForm's save method
Since you want to add the old and new quantities instead of overwriting them you need to take care of that before the form is saved. This should typically happen in the form's clean method.
The resulting ItemForm and createNewProduct view would then look like this:
class ItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ['item','quantity']
def clean(self):
cleaned_data = super().clean()
# if this Item already exists
if self.instance:
# add the old quantity to the new quantity
cleaned_data['quantity'] += self.instance.quantity
return cleaned_data
def createNewProduct(request):
if request.method == 'POST':
try:
dbItem = Item.objects.get(item=request.POST['item'])
except Item.DoesNotExist:
# get form for new Item
form = ItemForm(request.POST)
else:
# get form for existing Item
form = ItemForm(request.POST,instance=dbItem)
finally:
if form.is_valid():
form.save()
return redirect('success') # redirect on success
return redirect('failure') #redirect on failure
else:
form = ItemForm()
return render(request,'temp/add_item.html',{'form':form})

Related

Set initial value in a form in Django

I have a model in my application:
models.py:
class bdAccesorios(models.Model):
fdClienteAcc=models.CharField(max_length=35)
fdProveedorAcc=models.CharField(max_length=60)
fdSkuAcc=models.CharField(max_length=30)
fdNombreAcc=models.CharField(max_length=60)
fdCostoAcc=models.DecimalField(max_digits=8, decimal_places=2)
fdUnidadAcc=models.CharField(max_length=30)
fdExistenciaAcc=models.IntegerField()
fdAuxAcc=models.CharField(max_length=60, default="0")
Then, I have a form to add new entries to the model
class fmAccesorios(ModelForm):
class Meta:
model=bdAccesorios
fields='__all__'
What I can't accomplish is that the form starts with an initial value, so far what I have done in my views is this, but the field shows blank
views.py
def vwCrearAccesorio(request):
vrCrearAcc=fmAccesorios(initial={'fdClienteAcc':"foo"}) ###Here is the problem ###
if request.method == "POST":
vrCrearAcc=fmAccesorios(request.POST)
if vrCrearAcc.is_valid():
vrCrearAcc.save()
return redirect('/')
else:
vrCrearAcc=fmAccesorios()
return render(request,"MyApp/CrearAccesorio.html",{
"dtCrearAcc":vrCrearAcc
})
MORE INFO:
I know that I can use the following code in my form to set initial values
def __init__(self, *args, **kwargs):
super(fmAccesorios, self).__init__(*args, **kwargs)
self.fields['fdClienteAcc'].disabled = True
self.fields['fdClienteAcc'].initial = "foo"
But I can't use that, because I need the variable "foo" to change dynamically, my ultimate goal is to use
the request.user.username variable and then use that variable to get another value from another model
In your view you have to pass the current instance you need to the form like this:
def vwCrearAccesorio(request):
vrCrearAcc=fmAccesorios(initial={'fdClienteAcc':"foo"}) # this will not be used because you reassign `vrCrearAcc` later
if request.method == "POST":
vrCrearAcc=fmAccesorios(request.POST, initial={'fdClienteAcc':"foo"}) # pass it here
if vrCrearAcc.is_valid():
vrCrearAcc.save()
return redirect('/')
else:
vrCrearAcc=fmAccesorios(initial={'fdClienteAcc':"foo"}) # and here
return render(request,"MyApp/CrearAccesorio.html",{
"dtCrearAcc":vrCrearAcc
})

Set values for form field by query using filter

I want to set the item in dropdown using the query in the form. I want to add employee and the select company which using filter Is_Del= 0. I do not know how to set values for the drop down and where to write this query.
I tried to put in Forms.py, but it is not working.
This is form.py
class EmployeeCreateForm(forms.ModelForm):
class Meta:
model = Employee
fields = ('Emp_Name','Emp_company','Emp_Dept','Emp_Join_Date', 'Emp_End_Date')
def clean(self):
cleaned_data = super(EmployeeCreateForm, self).clean()
Emp_Name = cleaned_data.get('Emp_Name')
Emp_company = cleaned_data.get('Emp_company')
Emp_Dept = cleaned_data.get('Emp_Dept')
Emp_Join_Date = cleaned_data.get('Emp_Join_Date')
Emp_End_Date = cleaned_data.get('Emp_End_Date')
return cleaned_data
def __init__(self, *args, **kwargs):
super(EmployeeCreateForm,self).__init__(*args, **kwargs)
self.fields['Emp_company'].queryset = Company.objects.filter(Is_Del=0)
and below is my view.py
class EmployeeCraeteView(LoginRequiredMixin,SuccessMessageMixin,CreateView):
model=Employee
form = EmployeeCreateForm
success_message = " Employee Craeted successfully!"
success_url="../../company/all-companies"
template_name = 'employee_form.html'
fields =[
'Emp_Name','Emp_company','Emp_Dept','Emp_Join_Date',
'Emp_End_Date'
]
companies= Company.objects.filter(Is_Del=0)
def form_valid(self,form):
form.instance.Emp_Crt_By = self.request.user
if form.cleaned_data['Emp_Join_Date'] >= form.cleaned_data['Emp_End_Date']:
form.add_error('Emp_End_Date', 'Joining date should be less than Ending date')
return self.form_invalid(form)
return super(EmployeeCraeteView, self).form_valid(form)
I want to show only this companies in the form which are filtered by Is_Del =0
Your EmployeeCreateView is wrong:
Remove the attributes form, fields and companies
Add form_class = EmployeeCreateForm.
The reason is that form doesn't do anything in a CreateView (see here). To use a custom form class, you need to pass it to form_class.
Your CreateView was dynamically creating the form using a modelform_factory with the fields you defined (if you hadn't added those you'd have seen your mistake immediately) and so your EmployeeCreateForm is never instantiated.

get a multiple choice queryset in Django view and save it

I have a multiple choice field with a foreign key. I want to save which keeper was attending a training session and I want to list all keepers as a multiple choice field.
class AddAttendance(forms.ModelForm):
attendanceKeeper = Attendance.objects.only("keeper","present").all()
keeperValues = Attendance.objects.values_list("keeper__id", flat=True).distinct()
keeper = forms.ModelMultipleChoiceField(widget=forms.widgets.CheckboxSelectMultiple, queryset=Keeper.objects.filter(id__in=keeperValues, status=1))
class Meta:
model = Attendance
fields = ('keeper',)
def __init__(self, *args, **kwargs):
super(AddAttendance, self).__init__(*args, **kwargs)
self.initial["keeper"] = Keeper.objects.all()
However my problem is, I am not familiar how to handle a queryset in the view and how to loop through it and to save every instance with the value True or False.
I always get the value error that a queryset cannot be assigned
"Attendance.keeper" must be a "Keeper" instance
Can you help me how I access the queryset values and save them
def new_attendance(request, team_pk, package_pk):
if request.method == "POST":
form = AddAttendance(request.POST)
if form.is_valid():
for item in form:
attendance = item.save(commit=False)
attendance.keeper = get_object_or_404(AddAttendance.keeper)
attendance.team = get_object_or_404(Team, pk=team_pk)
attendance.created_date = timezone.now()
attendance.save()
return redirect(reverse('select_package', args=[package_pk, team_pk]))
else:
form = AddAttendance()
return render(request, 'attendance/new_attendance.html', {'form': form})
In the end I want to match keeper from the queryset and save True/False into the field present in my model
class Attendance(models.Model):
session = models.ForeignKey(Session)
keeper = models.ForeignKey(Keeper)
team = models.ForeignKey(Team)
present = models.BooleanField()
created_date = models.DateTimeField(default=timezone.now)
edited_date = models.DateTimeField(default=timezone.now)
You don't want a multiple choice field; you want a single choice. Only one keeper can be associated with each Attendance object.
You are doing a bunch of strange and unnecessary things here. You should remove most of this code, and use the ModelChoiceField which is the default for a ForeignKey. You don't want a checkbox widget either, since again that is for multiple choices; perhaps a radiobutton would be suitable.
class AddAttendance(forms.ModelForm):
class Meta:
model = Attendance
fields = ('keeper',)
widgets = {'keeper': forms.RadioSelect}
# remove the __init__ and the field definitions, you don't need them
...
form = AddAttendance(request.POST)
if form.is_valid():
attendance = item.save(commit=False)
attendance.team = get_object_or_404(Team, pk=team_pk)
attendance.created_date = timezone.now()
attendance.save()
return redirect(reverse('select_package', args=[package_pk, team_pk]))
There's no need to set the keeper explicitly in the view, since that's what the form is doing.

How get instance in a method clean from some forms

How can I get the Article instance in my model form's clean method? I tried too access self.instance but it is None. How do I get the previous field values?
model
class Article(models.Model):
name = models.CharField(max_length=25)
value = models.CharField(max_length=25)
forms
class ArticleForm(forms.ModelForm)
class Meta:
model = Article
fields = '__all__'
def clean(self):
cleaned_data = super().clean()
get_instance = self.instance
print(get_instance) and I get None
views
def test(request)
form = ArticleForm({'name':'test', 'value':'test'})
if form.is_valid():
print(1)
else:
print(form.errors)
You get None because you didn't instantiate the form with an instance.
form = ArticleForm({'name':'test', 'value':'test'})
If you instantiate the form with an instance, then you can access it with self.instance in the clean method.
article = Article.objects.get(pk=1)
form = ArticleForm({'name':'test', 'value':'test'}, instance=article)
However, note that cleaning the form alters the model instance. If you want the original values, you should refetch the instance from the database, e.g. original_instance = Art
def clean(self):
cleaned_data = super().clean()
if self.instance is not None and self.instance.pk is not None:
original_instance = Article.objects.get(pk=self.instance.pk)
else:
original_instance = None
...
If you only want to know which fields changed, and don't care about their original values, it would be simpler to use the changed_data attribute.

Changing a date into a checkbox in Django

I have a customer table that has an id, name, telephone and a date where the customer was set to inactive (called 'inactive_date').
In my form, I only want a checkbox to appear. If there is a date, then the checkbox is set. If the date is empty, then the checkbox is not set.
The only way I can set the checkbox so far is by doing the following:
class MyCustomerForm(ModelForm):
inactive_checkbox = forms.BooleanField(initial='checked')
I tried to use a clean or an init method to be able to set the checkbox based on a condition, but these attempts down below have been unsuccessful. My form always return the checkbox as unset regardless of the contents of the inactive_date.
class MyCustomerForm(ModelForm):
inactive_checkbox = forms.BooleanField()
def clean(self, *args, **kwargs):
inactive_date = self.cleaned_data['inactive_date']
if (inactive_date != None):
self.fields['inactive_checkbox'] = 'checked'
return self.cleaned_data
def __init__(self, *args, **kwargs):
super(myCustomerForm, self).__init__(*args, **kwargs)
inactive_date = self.cleaned_data['inactive_date']
if (inactive_date != None):
self.fields['inactive_checkbox'] = 'checked'
class Meta:
model = MyCustomer
fields = ['customer_id','customer_name','customer_phone',
'inactive_checkbox']
Any idea what is wrong with my code or is there perhaps a better way to control my inactive_date through a checkbox?
Try with initial:
https://docs.djangoproject.com/en/1.8/ref/forms/api/#dynamic-initial-values
In your view check the state of inactive_date field and then pass the result to the form via initial argument.
Hope it can help.
I wanted to post my new form based on jorlugaqui's answer as a comment, but comments are very limited in terms of formatting and length.
So here's my new form and view based on jorlugaqui's recommendation:
New form:
class MyCustomerForm(ModelForm):
inactive_checkbox = forms.BooleanField(required=False, label="Inactive")
class Meta:
model = MyCustomer
fields = ['customer_id','customer_name','customer_phone']
New view:
def customer_detail(request):
id = request.GET.get('id')
item = MyCustomer.objects.get(customer_id=id)
if request.method == 'POST':
form = MyCustomerForm(request.POST, instance=item, initial={'inactive_checkbox': item.inactive})
if form.is_valid():
inactive = request.POST.get('inactive_checkbox')
modified_item = form.save(commit=False)
if inactive:
if modified_item.inactive == None:
modified_item.inactive = datetime.datetime.now().strftime("%Y-%m-%d")
else:
modified_item.inactive = None
modified_item.save()
else:
form = MyCustomerForm(initial={'inactive_checkbox': item.inactive}, instance=item)
return render(request, 'basic_detail.html', {'id': id, 'form': form})
Create a new widget with the desired behavior. The default CheckboxInput widget (tested in django 3.2) properly checks or unchecks the checkbox based on if the field is a datetime or None. Then, you just override the response returned from user input:
from django.forms.widgets import CheckboxInput
from django.utils import timezone
class DateTimeCheckboxInput(CheckboxInput):
def value_from_datadict(self, data, files, name):
# cast the boolean returned to a timestamp or None
value = super().value_from_datadict(data, files, name)
return timezone.now() if value else None
Then, you can use this widget in any django model form
class MyCustomerForm(ModelForm):
class Meta:
model = MyCustomer
fields = ['inactive_checkbox']
widgets = {
"inactive_checkbox": widgets.DateTimeCheckboxInput,
}

Categories

Resources