How to provide initial data to ForeignKey in ModelFormSet? - django

So I have this thing.
These are the relationships of my models
To define an event you have a Place, a QR Link and an ImageAlbum which have up to five Images.
An event may or may not have a Place
An event MUST have the main image which is marked by the main attr in the Image Form.
All the forms are in the same page, they all are processed with a single Submit
Now my troubles begin and end in the Forms Department, i have several Model Forms:
#FORMS FILE
class EventForm(ModelForm):
date_time_start = forms.DateTimeField(input_formats=["%Y-%m-%d"], required=True, widget=forms.DateTimeInput(format="%Y-%m-%d", attrs={"id": "cal_start", "placeholder": "yyyy-mm-dd"}))
has_place = forms.BooleanField()
class Meta:
model = Event
fields = ['title', 'subtitle', 'phrase', 'date_time_start', 'prices', 'has_place']
class BaseImageFormSet(BaseModelFormSet):
def clean_main(self):
"""Checks that there is only one main image"""
if any(self.errors):
return
boolean_arr = []
for form in self.forms:
if self.can_delete and self._should_delete_form(form):
continue
main_data = form.cleaned_data.get('main')
main = main_data if main_data is not None else False
if sum(boolean_arr) > 1:
raise ValidationError('There can only be one main image.')
boolean_arr.append(main)
class PlaceForm(ModelForm):
class Meta:
model = EventPlace
exclude = ['uuid', 'event']
class QRForm(ModelForm):
class Meta:
model = EventQR
exclude = ['uuid', 'event']
My headache is with the Images, I have to make a ModelFormSet of Images but I have to create first the album which connects the images to the event.
Here are my views:
#VIEWS FILE
class NewEventView(BaseView):
def get(self, request, *args):
ImageFormSet = modelformset_factory(
EventImage, fields=('image', 'main'),
extra=5, max_num=5, formset= BaseImageFormSet)
image_formset = ImageFormSet(prefix='images')
event_form = EventForm(prefix='event')
place_form = PlaceForm(prefix='place')
qr_form = QRForm(prefix='qr')
context = {'forms': {'Evento': event_form, 'Lugar': place_form,
'QR Links': qr_form}, 'image_formset': image_formset, }
return render(request, "events/events_add.html", context)
def post(self, request):
event_form = EventForm(request.POST, prefix='event')
if event_form.is_valid():
new_event = event_form.save(commit=False)
if event_form.cleaned_data['has_place']:
place_form = PlaceForm(request.POST, prefix='place')
if place_form.is_valid():
qr_form = QRForm(request.POST, prefix='qr')
if qr_form.is_valid():
new_album = EventAlbum(event=new_event)
ImageFormSet = modelformset_factory(
EventImage, fields=('image', 'main'),
extra=5, max_num=5, formset= BaseImageFormSet)
image_formset = ImageFormSet(request.POST, request.FILES,
prefix='images')
if image_formset.is_valid():
event_form.save()
new_album.save()
new_place = place_form.save(commit=False)
new_place.event = new_event
new_qr = qr_form.save(commit=False)
new_qr.event = new_event
new_place.save()
new_qr.save()
image_formset.save()
return HttpResponse('Ok', content_type='text')
else:
return Response(image_formset.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(qr_form.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(place_form.errors, status=status.HTTP_400_BAD_REQUEST)
else:
qr_form = QRForm(request.POST, prefix='qr')
if qr_form.is_valid():
new_album = EventAlbum(event=new_event)
ImageFormSet = modelformset_factory(
EventImage, extra=5, max_num=5, formset=BaseImageFormSet)
image_formset = ImageFormSet(
request.POST, request.FILES, prefix='images')
if image_formset.is_valid():
event_form.save()
new_album.save()
image_formset.save()
qr_form.save()
return HttpResponse('Ok', content_type='text')
else:
return Response(image_formset.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response(qr_form.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(event_form.errors, status=status.HTTP_400_BAD_REQUEST)
How would you do about saving the images?
And the whole event with its dependencies.
Please help i am stuck by now

Related

Validation of an inline formset conditional to the value of a field from the parent form

I want to use custom validation in a django inline formset (form_date_event) so that I get different validation according to one value on the parent (form_event) form.
Specifically, I'd like the form_date_event inline formset to accept an empty venue if the type of the form_event is 'recording release'. How can I achieve that? The type = cleaned_data.get('type') below doesn't work, I suppose because it's getting the clean data of the formset, not of the parent form.
models.py:
class Venue(models.Model):
venue_name = models.CharField(max_length=50)
class Event(models.Model):
EV_TYPE = (
('performance', 'performance'),
('recording release', 'recording release'),
('other', 'other'),
)
title = models.CharField(max_length=200)
type = models.CharField(max_length=20, choices=EV_TYPE, default="performance")
class dateEvent(models.Model):
venue = models.ForeignKey(Venue, on_delete=models.CASCADE,null=True,blank=True)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
start_date_time = models.DateTimeField(auto_now=False, auto_now_add=False)
forms.py:
class EventForm(forms.ModelForm):
class Meta:
model = Event
fields = [
'type',
'title',
]
views.py:
class BaseDateEventFormSet(BaseInlineFormSet):
def clean(self):
cleaned_data = super().clean()
type = cleaned_data.get('type')
if type == 'recording release':
self.add_error('repertoire', _("This field is required"))
return cleaned_data
def event_edit_view(request, id):
event = get_object_or_404(Event, id=id)
active_user = request.user
form_event = EventForm(request.POST or None, instance=event)
DateEventFormSet = inlineformset_factory(Event, dateEvent, extra=5, can_delete=True, fields=('event', 'start_date_time', 'venue', 'link', 'link_description'),
form_date_event = DateEventFormSet(request.POST or None, instance=Event.objects.get(id=id), prefix="dateEvent", queryset=dateEvent.objects.filter(event__id=id))
context = {
'event': event,
'id': event.id,
'form_event': form_event,
'form_date_event': form_date_event,
}
if request.method == "POST":
if form_event.is_valid() and request.POST['action'] == 'submit':
if form_date_event.is_valid():
flag=False
evt_type = form_event.cleaned_data.get('type')
for form in form_date_event:
print(form.cleaned_data.get('venue'))
if form.cleaned_data.get('venue') is None and evt_type !="recording release":
flag = True
break
if flag:
messages.error(request,'Venue can be blank only for recording release events')
return render(request, "events/event-edit.html", context)
else:
form_event.save()
form_date_event.save()
return redirect('my-events')
else:
raise forms.ValidationError(form_date_event.errors)
elif form_event.is_valid() and form_date_event.is_valid() and request.POST['action'] == 'update':
form_event.save()
form_date_event.save()
else:
raise forms.ValidationError([form_event.errors, form_date_event.errors])
return render(request, "events/event-edit.html", context)
dateEvent model modify the venue field, venue = models.ForeignKey(Venue, on_delete=models.CASCADE,null=True,blank=True)
in event_edit_view() function, add the following lines of code
if form_date_event.is_valid() and form_event.is_valid()
flag=False
evt_type = form_event.cleaned_data.get('type')
for form in form_date_event:
if form.cleaned_data.get('venue') is None and evt_type !="recording release":
flag = True
break
if flag:
messages.error(request,'Venue can be blank only for recording release events')
return render(request,myhtml.html,{'formset':form_date_event,'form':form_event})
else:
form_event.save()
form_date_event.save()

signals post_save called twice after updating data in class base view django

So i've been stack on this problem that everytime I update my stock in orders it called twice for updating I use the class base view built in Django but in models I have a post signal which it removing the stock of the product. I already use the dispatch_uid but it's not working or any solution that I found on internet not working on my problem.
here is my class base view for updating:
class ItemQtyUpdateClassBaseView(UpdateView):
template_name = 'components/update/item.html'
form_class = ItemForm
def get_context_data(self, **kwargs):
kwargs['context_title'] = 'Update Order Qty'
return super(ItemQtyUpdateClassBaseView, self).get_context_data(**kwargs)
def form_valid(self, form):
try:
order_item = OrderModel.objects.get(pk = self.kwargs[self.pk_url_kwarg])
fetch_product = ProductsModel.objects.get(product_name = order_item.product_name)
print(fetch_product.qty)
if fetch_product.dough != None:
if int(fetch_product.dough.qty) <= 0:
messages.error(self.request, 'Not enough qty to order.')
return self.form_invalid(form)
if int(fetch_product.qty) <= 0:
messages.error(self.request, 'Not enough qty to order.')
return self.form_invalid(form)
else:
self.object = form.save()
messages.success(self.request, 'Successfully update qty.')
except Exception as e:
messages.error(self.request, e)
return self.form_invalid(form)
return super(ItemQtyUpdateClassBaseView, self).form_valid(form)
def form_invalid(self, form):
return super().form_invalid(form)
def get_queryset(self):
return OrderModel.objects.filter(pk = self.kwargs[self.pk_url_kwarg])
def get_success_url(self):
return reverse_lazy('core:createorder', kwargs = {
'typeoforder': self.kwargs['typeoforder']
})
and here is my model with signal post_save:
class OrderModel(models.Model):
date = models.DateField(auto_now = True)
invoice_no = models.ForeignKey(InvoiceModel, on_delete = models.CASCADE)
product_name = models.CharField(max_length = 150, blank = False, null = True)
price = models.DecimalField(max_digits = 10, decimal_places = 2)
qty = models.FloatField(default = 1)
class Meta:
db_table = 'orders'
ordering = ['-pk']
#receiver(post_save, sender = OrderModel, dispatch_uid='signals.ordermodel_removeqty')
def removeQty(sender, instance = None, created = False, **kwargs):
product = ProductsModel.objects.get(product_name = instance.product_name)
if product.dough != None:
dough = DoughModel.objects.get(pk = product.dough.pk)
dough.qty = abs(dough.qty - (product.qty * instance.qty))
dough.save()
else:
product.qty = abs(product.qty - instance.qty)
product.save()
You are calling save() method two times in form_valid() method :
self.object = form.save()
super(ItemQtyUpdateClassBaseView, self).form_valid(form)
You have to remove the below line from form_valid() method:
self.object = form.save()
See what form_valid(form)(Django Docs) method do:
Saves the form instance, sets the current object for the view, and
redirects to get_success_url().

Validating a formset using data from another form

I'm having to do some validation across both a form and formset. The £££ amount in the form must equal the sum of the amounts in the formset.
After a lot of Googling I found a solution where I add a custom init to the baseformset as follows:
class BaseSplitPaymentLineItemFormSet(BaseFormSet):
def __init__(self, cr=None, *args, **kwargs):
self._cr = cr
super().__init__(*args, **kwargs)
def clean(self):
if any(self.errors):
return
sum_dr = 0
for form in self.forms:
sum_dr += form.cleaned_data.get('dr')
if sum_dr != float(self._cr):
raise forms.ValidationError('The amount entered needs to equal the sum of the split payments.')
I then pass the amount value from the form when the formset is instantiated, so that the value can be used in the formset validation:
lineitem_formset = LineItemFormSet(form.data['amount'], request.POST)
This worked great for the create_new view which uses formset_factory(). This morning I wrote the update view using inline_formsetfactory(), but I now get an error:
__init__() got an unexpected keyword argument 'instance'
I only have a basic understanding of how the custom init works, so I can't find a solution to this error.
Forms.py:
class SplitPaymentForm(forms.Form):
date = forms.DateField(widget=DateTypeInput())
account = GroupedModelChoiceField(queryset=Ledger.objects.filter(coa_sub_group__type='a').order_by('coa_sub_group__name','name'), choices_groupby = 'coa_sub_group')
store = forms.CharField(required=True)
amount = forms.DecimalField(decimal_places=2)
class SplitPaymentLineItemForm(ModelForm):
ledger = GroupedModelChoiceField(queryset=Ledger.objects.all().order_by('coa_sub_group__name', 'name'), choices_groupby = 'coa_sub_group', empty_label="Ledger", required=True)
project = forms.ModelChoiceField(queryset=Project.objects.filter(status=0), empty_label="Project", required=False)
class Meta:
model = LineItem
fields = ['description','project', 'ledger','dr',]
# This init disallows empty formsets
def __init__(self, *arg, **kwarg):
super(SplitPaymentLineItemForm, self).__init__(*arg, **kwarg)
self.empty_permitted = False
class BaseSplitPaymentLineItemFormSet(BaseFormSet):
def __init__(self, cr=None, *args, **kwargs):
self._cr = cr
super().__init__(*args, **kwargs)
def clean(self):
if any(self.errors):
return
sum_dr = 0
for form in self.forms:
sum_dr += form.cleaned_data.get('dr')
if sum_dr != float(self._cr):
raise forms.ValidationError('The amount entered needs to equal the sum of the split payments.')
Views.py:
def split_payments_new(request):
LineItemFormSet = formset_factory(SplitPaymentLineItemForm, formset=BaseSplitPaymentLineItemFormSet, extra=2)
if request.method == 'POST':
form = SplitPaymentForm(request.POST)
lineitem_formset = LineItemFormSet(form.data['amount'], request.POST)
if form.is_valid() and lineitem_formset.is_valid():
q0 = JournalEntry(user=request.user, date=form.cleaned_data['date'], type="SP",)
q1 = LineItem(journal_entry=q0, description=form.cleaned_data['store'], ledger=form.cleaned_data['account'], cr=form.cleaned_data['amount'])
q0.save()
q1.save()
for lineitem in lineitem_formset:
q2 = LineItem(journal_entry=q0,description=lineitem.cleaned_data.get('description'),ledger=lineitem.cleaned_data.get('ledger'),project=lineitem.cleaned_data.get('project'),dr=lineitem.cleaned_data.get('dr'))
q2.save()
messages.success(request, "Split payment successfully created.")
return HttpResponseRedirect(reverse('journal:split_payments_show_detail', kwargs={'pk': q0.id}) )
else:
form = SplitPaymentForm(initial = {'date': datetime.date.today().strftime('%Y-%m-%d')})
lineitem_formset = LineItemFormSet()
return render(request, 'journal/split_payments_new.html', {'form': form, 'formset': lineitem_formset})
def split_payments_update(request, pk):
journal_entry = get_object_or_404(JournalEntry, pk=pk, type="SP")
lineitem = LineItem.objects.get(journal_entry=journal_entry.id, dr__isnull=True)
initial = {
'date': journal_entry.date.strftime('%Y-%m-%d'),
'account': lineitem.ledger,
'store': lineitem.description,
'amount': lineitem.cr,
}
form = SplitPaymentForm(initial=initial)
LineItemFormSet = inlineformset_factory(JournalEntry, LineItem, form=SplitPaymentLineItemForm, formset=BaseSplitPaymentLineItemFormSet, extra=0)
lineitem_formset = LineItemFormSet(instance=journal_entry)
if request.method == 'POST':
lineitem_formset = LineItemFormSet(form.data['amount'], request.POST, instance=journal_entry)
form = SplitPaymentForm(request.POST)
if lineitem_formset.is_valid() and form.is_valid():
lineitem_formset.save()
journal_entry.date = form.cleaned_data['date']
lineitem.ledger = form.cleaned_data['account']
lineitem.description = form.cleaned_data['store']
lineitem.cr = form.cleaned_data['amount']
journal_entry.save()
lineitem.save()
messages.success(request, "Split payment successfully updated.")
return HttpResponseRedirect(reverse('journal:split_payments_show_detail', kwargs={'pk': journal_entry.id}) )
return render(request, 'journal/split_payments_update.html',{'form': form, 'formset': lineitem_formset, 'journal_entry': journal_entry})
Solved. Just had to use BaseInlineFormSet.

Django update form fields return as blank

I have the following view
class ProductUpdateView(BaseProductUpdateView):
form_class = ProductForm
slug_field = "id"
def form_valid(self, form):
form.instance.hotel = form.cleaned_data["hotel"]
form.instance.parent = form.cleaned_data["parent"]
return super(ProductUpdateView, self).form_valid(form)
and following form:
class ProductForm(BaseProductForm):
hotel = forms.ModelChoiceField(queryset=Hotel.objects.all(), widget=forms.TextInput)
parent = forms.ModelChoiceField(queryset=Product.objects.all(), widget=forms.TextInput, required=False)
class Meta:
model = Product
exclude = ('slug', 'parent', 'hotel', 'status', 'score', 'product_class',
'recommended_products', 'related_products',
'product_options', 'attributes', 'categories')
When I save the form, form saved successfully. I can see the saved values of hotel and parent in admin, but when I re-open the update form page, other fields returns but hotel and parent fields return blank. Any ideas?
I handled it like below
views.py
class ProductUpdateView(BaseProductUpdateView):
form_class = ProductForm
slug_field = "id"
def get_form_kwargs(self, **kwargs):
kwargs = super(ProductUpdateView, self).get_form_kwargs(**kwargs)
kwargs['initial']['hotel'] = self.get_object().hotel.id if \
self.get_object().hotel else None
kwargs['initial']['parent'] = self.get_object().parent.id if \
self.get_object().parent else None
return kwargs
def form_valid(self, form):
form.instance.hotel = form.cleaned_data["hotel"]
form.instance.parent = form.cleaned_data["parent"]
return super(ProductUpdateView, self).form_valid(form)
forms.py
class AutoCompleteModelChoiceField(forms.ModelChoiceField):
widget = forms.TextInput
def clean(self, value):
value = super(AutoCompleteModelChoiceField, self).clean(value)
return value
class ProductForm(BaseProductForm):
hotel = AutoCompleteModelChoiceField(queryset=Hotel.objects.all())
parent = AutoCompleteModelChoiceField(queryset=Product.objects.all(),
required=False)

Two model forms in one view/template in Django using CBV

Can someone show the example of usage of two model forms in one view/template? The task is to process more than one form in one CBV, where model of the second form has FK to first form model, so value from select widget from first form must be processed as value for object, created in second form.
My forms looks like this:
class CompanyEditForm(ModelForm):
name = CharField(label="Наименование", required=True)
type = ModelChoiceField(queryset=CompanyTypes.objects.all(), label="Тип компании", empty_label=None, initial=3)
description = CharField(label="Описание компании", required=False, widget=forms.Textarea(attrs={'cols': 40, 'rows':3}))
www = CharField(label="WEB сайт", required=False)
class Meta:
model = Companies
fields = ('type', 'name', 'description', 'www')
class BranchEditForm(ModelForm):
name = CharField(label="Наименование офиса", required=True)
type = ModelChoiceField(queryset=BranchTypes.objects.all(), label="Тип отделения", empty_label=None, initial=1)
class Meta:
model = Branches
exclude = ('company', 'address')
class AddressEditForm(ModelForm):
postalcode = IntegerField(label="Почтовый код", required=False)
city = CharField(label="Город", required=True)
street = CharField(label="Улица", required=True)
app = CharField(label="Дом", required=True)
app_extra = CharField(label="Корпус / Строение", required=False)
comment = CharField(label="Примечание к адресу", required=False)
exclude = ('company',)
class Meta:
model = Addresses
fields = ('postalcode', 'city', 'street', 'app', 'app_extra', 'comment')
UPDATE
I wrote this mixin:
class MultiFormCreate(FormMixin, TemplateResponseMixin, View):
formconf = None
def get_form_classes(self):
form_classes = {}
for key, params in self.formconf.items():
form_classes[key] = params.formclass
return self.form_classes
def get_initial(self, classname):
inicial = {}
if 'inicial' in self.formconf[classname]:
inicial = self.formconf[classname]['inicial'].copy()
return inicial
def get_form_kwargs(self, classname):
kwargs = {'initial': self.get_initial(classname), 'prefix': classname}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def get_forms(self):
for classname, params in self.formconf.items():
log.info("Name: %s, Params: %s" % (classname, params))
return dict(
[(classname, params['formclass'](**self.get_form_kwargs(classname))) for classname, params in self.formconf.items()])
def get(self, request, *args, **kwargs):
forms = self.get_forms()
return self.render_to_response(self.get_context_data(forms=forms))
def get_success_url(self):
if self.success_url:
url = force_text(self.success_url)
else:
raise ImproperlyConfigured(
"No URL to redirect to. Provide a success_url.")
return url
then in a view i only need to write processing in post:
class CompanyCreate(MultiFormCreate):
template_name = 'company/edit.html'
success_url = '/forbidden/'
formconf = {
'company': {'formclass': CompanyEditForm, 'inicial': {'name': "TESTNAME"}},
'branch': {'formclass': BranchEditForm},
'address': {'formclass': AddressEditForm}
}
def post(self, request, *args, **kwargs):
forms = self.get_forms()
cform = forms['company']
aform = forms['address']
bform = forms['branch']
if cform.is_valid() and aform.is_valid() and bform.is_valid():
''' Creating main form form object (by saving tthe form) '''
company_object = cform.save()
''' Creating dependant object '''
address_object = aform.save(commit=False)
branch_object = bform.save(commit=False)
''' assigning dependent fields '''
address_object.company = company_object
''' saving dependent _object_ '''
address_object.save()
''' managing another dependent fields '''
branch_object.company = company_object
branch_object.address = address_object
''' saving last object '''
branch_object.save()
return HttpResponseRedirect(self.get_success_url())
else:
forms = self.get_forms()
return self.render_to_response(self.get_context_data(forms=forms))
Code for view file :
if request.method == "POST":
company_form = CompanyEditForm(request.POST)
if company_form.is_valid():
company_object = company_form.save()
post_data = request.POST.copy()
branch_form = BranchEditForm(post_data)
branch_form.data['company'] = company_object
if branch_form.is_valid():
branch_object.save()
rest implement your business logic ..