Django save multiple modelforms in view - django

I am probably missing something very obvious here, but can't get this to work.
I have 2 models (Organization and Address), 2 forms (One for each model) and 1 view where I want to save the Organization with the Address as the child.
Models:
class Address(models.Model):
address = models.CharField(max_length=255, verbose_name=_("Address"))
postal_code = models.CharField(max_length=20, verbose_name=_("Postal_code"))
city = models.CharField(max_length=255, verbose_name=_("City"))
class Organization(models.Model):
name = models.CharField(max_length=100, verbose_name=_("Name"))
address = models.OneToOneField(Address, on_delete=models.CASCADE, verbose_name=_("Address"))
owner = models.ForeignKey("users.User", related_name="organizations", verbose_name=_("Owner"))
Forms:
class AddressForm(forms.ModelForm):
class Meta:
model = Address
fields = ["address", "postal_code", "city"]
def __init__(self, *args, **kwargs):
super(AddressForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_tag = False
self.helper.disable_csrf = True
class OrganizationForm(forms.ModelForm):
class Meta:
model = Organization
fields = ["name", ]
def __init__(self, *args, **kwargs):
super(OrganizationForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_tag = False
self.helper.disable_csrf = True
And finally the view
class OrganizationCreateView(LoginRequiredMixin, TemplateView):
template_name = "organizations/organization_form.html"
def get_organization_form(self, data=None):
return OrganizationForm(data)
def get_address_form(self, data=None):
return AddressForm(data)
def get(self, request, *args, **kwargs):
ctx = self.get_context_data(organization_form=OrganizationForm(),
address_form=AddressForm())
return self.render_to_response(ctx)
def post(self, request, *args, **kwargs):
organization_form = self.get_organization_form(data=request.POST)
address_form = self.get_address_form(data=request.POST)
if organization_form.is_valid() and address_form.is_valid():
return self.forms_valid(organization_form, address_form)
return self.forms_invalid(organization_form, address_form)
def forms_valid(self, organization_form, address_form):
address = address_form
address.country = "DE"
address.save()
organization = organization_form
organization.save(commit=False)
organization.owner = self.request.user
organization.address = address
organization.save()
return redirect("list")
def forms_invalid(self, organization_form, address_form):
ctx = self.get_context_data(organization_form=self.get_organization_form(organization_form),
address_form=self.get_address_form(address_form))
return self.render_to_response(ctx)
So I do save the Address, next the Organization (with commit=False), add the Address to the organization, save the organization, and presto: an Exception
Exception Type: IntegrityError at /organizations/create/
Exception Value: null value in column "address_id" violates not-null constraint
DETAIL: Failing row contains (9, slkdfjlsdfk, lkdjflskdjflsd, , llksjjdlfkjsdlfkjsldkfj, f, null, null, null, null, null, null, null, null, null, null).
As I already mentioned, it is probably something very obvious, but can't find it. Can someone help me out here?

You're slightly confused about how to create a model instance from a form. The instance is returned from the call to form.save(); it's that instance to which you need to assign the address and user.
def forms_valid(self, organization_form, address_form):
address = address_form.save(commit=False)
address.country = "DE"
address.save()
organization = organization_form.save(commit=False)
organization.owner = self.request.user
organization.address = address
organization.save()
return redirect("list")

Related

How to provide a queryset for a foreign key field in django inline_formset?

I have the below forms:
class Purchase_form(forms.ModelForm):
class Meta:
model = Purchase
fields = ('date','party_ac', 'purchase')
widgets = {
'date': DateInput(),
}
def __init__(self, *args, **kwargs):
self.Company = kwargs.pop('company', None)
super(Purchase_form, self).__init__(*args, **kwargs)
self.fields['date'].widget.attrs = {'class': 'form-control',}
self.fields['party_ac'].queryset = Ledger1.objects.filter(company = self.Company)
self.fields['purchase'].queryset = Ledger1.objects.filter(Company = self.Company)
class Stock_Totalform(forms.ModelForm):
class Meta:
model = Stock_Total
fields = ('stockitem')
def __init__(self, *args, **kwargs):
self.Company = kwargs.pop('Company', None)
super(Stock_Totalform, self).__init__(*args, **kwargs)
self.fields['stockitem'].widget.attrs = {'class': 'form-control select2',}
self.fields['Total_p'].widget.attrs = {'class': 'form-control',}
Purchase_formSet = inlineformset_factory(Purchase, Stock_Total,
form=Stock_Totalform, extra=6)
My models:
class Purchase(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
company = models.ForeignKey(company,on_delete=models.CASCADE,null=True,blank=True)
date = models.DateField(default=datetime.date.today,blank=False, null=True)
party_ac = models.ForeignKey(Ledger1,on_delete=models.CASCADE,related_name='partyledger')
purchase = models.ForeignKey(Ledger1,on_delete=models.CASCADE,related_name='purchaseledger')
class Stock_Total(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,null=True,blank=True)
company = models.ForeignKey(Company,on_delete=models.CASCADE,null=True,blank=True)
purchases = models.ForeignKey(Purchase,on_delete=models.CASCADE,null=True,blank=False,related_name='purchasetotal')
stockitem = models.ForeignKey(Stockdata,on_delete=models.CASCADE,null=True,blank=True,related_name='purchasestock')
I want to make a queryset for the inline form field stockitem that it will filter the result in the form according to request.user and company as I have done it for the normal Purchase_form(I have done it using get_form_kwargs(self) method in the views for normal form).
I am having a difficulty to do a queryset in inlineform.
I want to do something like this in my inline_form:
self.fields['stockitem'].queryset = Stockdata.objects.filter(company = self.Company)
As I have done in normal forms.
Any idea anyone how to do this?
Thank you
To filter a field in the formset you have to override the add_fields method.
class PurchaseFormSet(forms.BaseInlineFormSet):
def __init__(self, *args, **kwargs):
self.company = kwargs.pop('company', None)
super().__init__(*args, **kwargs)
def add_fields(self, form, index):
super().add_fields(form, index)
form.fields['stockitem'].queryset = StockData.objects.filter(company=self.company)
Worth noting in your example you could filter the queryset in the child form's __init__ method. All of the form field attributes are also passed to the formset.

Two fields related in Django

I need to update my table every time a new value of "sku" is entered (not to create a new entry), but it does have to happen only if the "client" selected is the same. If the "client" is different, then the model should add a new object with the same "sku", but with different "clients".
I have tried to do the following in my models.py:
class ProductList(models.Model):
id_new = models.IntegerField(primary_key=True)
sku = models.CharField(primary_key=False, max_length=200)
client = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
name = models.CharField(max_length=256)
description = models.CharField(max_length=1000)
storage = models.CharField(max_length=256)
cost_price = models.CharField(max_length=256)
sell_price = models.CharField(max_length=256)
ncm = models.CharField(max_length=256)
inventory = models.IntegerField(null=True)
class Meta:
unique_together = (('sku', 'client'),)
But it is not working. How can I make that work?
You can try like this:
# form
class MyForm(forms.ModelForm):
class Meta:
model = ProductList
def save(self, *args, **kwargs:
client = self.cleaned_data.get('client') # get client from form cleaned_data
if hasattr(self.instance, 'pk') and self.instance.client != client: # check if client match's already existing instance's client
self.instance.pk = None # make a duplicate instance
self.instance.client = client # change the client
return super(MyForm, self).save(*args, **kwargs)
# views.py
# ...
def my_view(request, id):
instance = get_object_or_404(ProductList, id=id)
form = MyForm(request.POST or None, instance=instance)
if form.is_valid():
form.save()
return redirect('next_view')
return render(request, 'my_template.html', {'form': form})
Update
Um you can override the model as well. you can try like this:
# Untested Code but should work
def save(self, *args, **kwargs):
if self.pk:
current_instance = self.__class__.objects.get(pk=self.pk)
if current_instance.client != self.client:
self.pk = None
return super(ProductList, self).save(*args, **kwargs)

Django - field missing from cleaned data

I am using model form and I'm trying to bypass validation of one particular field.
I have ascertained that I need use the clean() method to bypass the field. however when im printing out the cleaned data the assigned_subnets field is not in the dictionary, it is missing
actions:
I create the assigned subnet field manually in forms.py. Then using jquery I alter that form field and add more select options to it post all the options. The additional values posted were not part of the original field choices, hence the error.
output from print:
{'site_data': None, 'switches': None, 'hostname': 'STR--RTR-01', 'template': <ConfigTemplates: STR-RTR-01>, 'model': <DeviceModel: Cisco - 4431>, 'install_date': datetime.date(2016, 5, 26), 'ospf_area': None, 'snmp_data': <SNMPData: XXXX>, 'available_subnets': <QuerySet []>}
forms.py
class DeviceForm(forms.ModelForm):
class Meta:
model = DeviceData
fields = ['site_data', 'switches', 'hostname', 'template', 'model', 'install_date','ospf_area','snmp_data']
def clean(self):
super(DeviceForm, self).clean()
print(self.cleaned_data)
if self.cleaned_data.get('assigned_subnets') in self._errors:
del self._errors['assigned_subnets']
return self.cleaned_data
def __init__(self, *args, **kwargs):
site_id = kwargs.pop('site_id', None)
device_id = kwargs.pop('device_id', None)
self.is_add = kwargs.pop("is_add", False)
super(DeviceForm, self).__init__(*args, **kwargs)
devicesubnet = Subnets.objects.filter(devicesubnets__device_id=device_id)
sitesubnet = Subnets.objects.filter(sitesubnets__site_id=site_id)
common_subnets = list(set(devicesubnet) & set(sitesubnet))
subnet_id_list = []
for s in common_subnets: subnet_id_list.append(s.id)
available_subnet_data = sitesubnet.exclude(id__in=subnet_id_list)
assigned_choices = []
devicesubnet_data = DeviceSubnets.objects.filter(device_id=device_id)
for choice in devicesubnet_data:
assigned_choices.append((choice.subnet.id,choice.subnet.subnet))
self.fields['available_subnets'] = forms.ModelMultipleChoiceField(
label='Available Subnets',
queryset=available_subnet_data,
widget = forms.SelectMultiple(
attrs = {'class': 'form-control', 'size' : '15'}
)
)
self.fields['assigned_subnets'] = forms.MultipleChoiceField(
label='Assigned Subnets',
choices=assigned_choices,
widget = forms.SelectMultiple(
attrs = {'class': 'form-control', 'size' : '15'}
)
)
self.fields['available_subnets'].required = False
self.fields['assigned_subnets'].required = False
self.helper = FormHelper(self)
self.helper.form_id = 'device_form'
self.helper.form_method = 'POST'
...
views.py
class EditDevice(UpdateView):
model = DeviceData
form_class = DeviceForm
template_name = "config/device_form.html"
#method_decorator(user_passes_test(lambda u: u.has_perm('config.edit_device')))
def dispatch(self, *args, **kwargs):
self.site_id = self.kwargs['site_id']
self.site = get_object_or_404(SiteData, pk=self.site_id)
return super(EditDevice, self).dispatch(*args, **kwargs)
def get_success_url(self, **kwargs):
return reverse_lazy("config:device_details", args=(self.site_id,))
def form_valid(self, form):
form.instance.site_data = self.site
assigned_subnets = form.cleaned_data['assigned_subnets']
print(assigned_subnets)
return super(EditDevice, self).form_valid(form)
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs()
kwargs['site_id'] = self.site_id
kwargs['device_id'] = self.object.pk
return kwargs
...
EDIT:
what im trying to do is like in the image below. I have a list of available subnets and a list of chosen (assigned) subnets.
I don think this form widget exists outside of Django admin? so Ive created the two fields manually and used jquery to move a subnet from available to assigned.
then I get the assigned subnets and update the DB. however I get the errors when I alter the assigned field

How to pass request.user to a model's clean method for a foreignkey unique_together validation?

I'm trying to run a validation where a user can't enter the same name_field twice but other users entering the same name will not interfere.
I tried using "unique_together = (("username","name_field"))" but when a user enters the same value twice the server generates an integrity error as opposed to rendering a warning message next to the form field.
then I tried overriding the clean() method in my model, Which runs fine if I only check "field_name" like so:
def clean(self):
existing = self.__class__.objects.filter(
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
But I am running into trouble when checking the username value, for instance:
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
I'm guessing due to it not being an actual field in the form its not present during the call to clean(). So my question is am I doing the validation correctly for this kind of problem? And how can I pass or where can I find the value for the current user from within a models clean method (in a safe way hopefully without adding fields to my form)?
views.py
def add_stuff(request):
if request.user.is_authenticated():
form = addStuff(request.POST or None)
if request.method == 'POST':
if form.is_valid():
sub_form = form.save(commit=False)
sub_form.username = request.user
sub_form.save()
return redirect('../somewhere_else/')
forms.py
class addStuff(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(addStuff, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
'name_field',
'type_field',
ButtonHolder(
Submit('Save', 'Save')
),
)
class Meta:
model = UserStuff
fields = ('name_field',
'type_field',
)
models.py
class UserStuff(models.Model):
username = models.ForeignKey(User)
name_field = models.CharField(max_length=24, blank=False,null=False)
type_field = models.CharField(max_length=24, blank=True,null=True)
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
def __unicode__(self):
return "%s For User: \"%s\" " % (self.name_field, self.username)
class Meta:
managed = True
db_table = 'my_db_table'
unique_together = (("username","name_field"))
Thanks for any insight!
I now am running the clean override from the form instead of the model (as recommended by Daniel). This has solved a bunch of issues and I now have a working concept:
models.py
class UserStuff(models.Model):
username = models.ForeignKey(User)
name_field = models.CharField(max_length=24, blank=False,null=False)
type_field = models.CharField(max_length=24, blank=True,null=True)
def clean(self):
existing = self.__class__.objects.filter(
username=self.username, ###This part crashes!!! (username not found)
name_field=self.name_field).count()
if existing > 0:
raise ValidationError({'name_field':self.username })
def __unicode__(self):
return "%s For User: \"%s\" " % (self.name_field, self.username)
class Meta:
managed = True
db_table = 'my_db_table'
forms.py
class addStuff(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(addStuff, self).__init__(*args, **kwargs)
initial = kwargs.pop('initial')
self.username = initial['user']
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
'name_field',
'type_field',
ButtonHolder(
Submit('Save', 'Save')
),
)
def clean(self):
cleaned_data = super(addStuff, self).clean()
name_field = self.cleaned_data['name_field']
obj = UserStuff.objects.filter(username_id=self.username.id,
name_field=name_field,
)
if len(obj) > 0:
raise ValidationError({'name_field':
"This name already exists!" } )
return cleaned_data
class Meta:
model = UserStuff
fields = ('name_field',
'type_field',
)
views.py
def add_stuff(request):
if request.user.is_authenticated():
form = addStuff(request.POST or None,
initial={'user':request.user})
if request.method == 'POST':
if form.is_valid():
sub_form = form.save(commit=False)
sub_form.username = request.user
sub_form.save()
return redirect('../somewhere_else/')
best of luck!

Creating form widgets based on instance values

I have the following model:
VARIABLE_CHOICES = (
('bool', 'On/Off'),
('date', 'Date'),
('float', 'Number'),
('text', 'Text'),
)
class LetterVariable(models.Model):
name = models.CharField(max_length=20)
type = models.CharField(max_length=5, choices=VARIABLE_CHOICES)
data = models.CharField(max_length=100)
I want to create a form that when I pass it an instance of LetterVariable from the db it will create the corrosponding widget for data bassed upon type.
Any ideas how I might do this?
class LetterVariableForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LetterVariableForm, self).__init__(*args, **kwargs)
if not self.instance:
raise Exception('You forgot the instance!');
if self.instance.type == 'something':
self.fields['data'].widget = forms.SomeWidget()