How to save parent objects within the form_valid - django

I have a class based view. I am trying to save an object with it's association. I have the following error :
NOT NULL constraint failed: boxes_suggestion.box_id
More explanation: I have a SuggestionBox (Model) and each Participant could add Comments into it. it's sort of a doodle clone.
detail.html
<h3>{{box.title}}</h3>
<form action="." method="post">{% csrf_token %}
{{ form.as_p }}
<input id="box_id_value" type="hidden" name="box_id_value" value='{{box.id}}' />
<input type="submit" class="btn btn-info" value="Add suggies 1" />
</form>
views.py
class SuggiesForm(FormView):
'''
Display the form
Otherwise
Process the form
1-Get the suggestion_box_id
2-Save the comment associated with that suggestion box.
'''
template_name = 'boxes/detail.html'
form_class = SuggestionForm
success_url = '/boxes/success'
box_instance = ''
def get_context_data(self, **kwargs):
'''
Retrieve the id of the Suggestion Box
'''
context = super(SuggiesForm, self).get_context_data(**kwargs)
#Find The appropriate Box so that user can add Suggies
context['box'] = Box.objects.get(pk=self.kwargs['pk'])
box_instance = context['box']
return context
def form_valid(self, form):
'''
'''
form.save(commit=False)
#box = box_instance
form.box = Box.objects.first()
form.participant = Suggestion.objects.first()
form.save()
return super(SuggiesForm, self).form_valid(form)
models.py
#python_2_unicode_compatible
class Box(models.Model):
"""
Box model
"""
def __str__(self):
return self.title
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=40, blank=True, null=True)
identify = models.BooleanField(default=False)
activate = models.BooleanField(default=False)
created_at = models.DateField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
#expiration_date = models.DateField(auto=Date in Future, blank=True, null=False)
#slug = AutoSlugField(_('slug'), populate_from="id")
#url(slug)
#python_2_unicode_compatible
class Participant(models.Model):
"""
Participant Model
"""
def __str__(self):
return self.email
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(blank=True, null=True, default='anonymous#email.com')
username = models.CharField(max_length=40, blank=True, null=True)
box = models.ForeignKey(Box, on_delete=models.CASCADE)
created_at = models.DateField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
#python_2_unicode_compatible
class Suggestion(models.Model):
"""
For adding comments (or suggestions)
"""
def __str__(self):
return self.comment[0:10]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
comment = models.CharField("",max_length=250, blank=True, null=True)
box = models.ForeignKey(Participant, on_delete=models.CASCADE)
created_at = models.DateField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)

You correctly used commit=False, but then added the attributes onto the form object itself instead of the object returned from the save. It should be:
object = form.save(commit=False)
object.box = Box.objects.first()
object.participant = Suggestion.objects.first()
object.save()

Related

Django - Form - ForeignKey - Hidden - Default value

I have a Hidden ForeignKey in an update form that I want to set to value of default value of 2 in my html form, but I can't get it to work.
forms.py
eval_sent_state = forms.ModelChoiceField(widget=forms.HiddenInput(), initial=2,queryset=models.EvalUrlSentState.objects.all())
The Html output i get:
<input type="hidden" name="eval_sent_state" value="1" id="id_eval_sent_state">
from views.py
class ClassSchoolTeacherUpdateView(generic.UpdateView):
model = models.ClassSchool
form_class = forms.ClassSchoolTeacherForm
pk_url_kwarg = "pk"
def get_object(self, queryset=None):
return models.ClassSchool.objects.get(class_random_key=self.kwargs.get("random"))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['username'] = self.kwargs.get('username')
context['random'] = self.kwargs.get('random')
return context
from models.py:
class ClassSchool(models.Model):
# Relationships
eval_sent_state = models.ForeignKey("EvalUrlSentState", default=1, on_delete=models.SET_NULL, blank=True, null=True)
# Fields
class_name = models.CharField(max_length=100)
class_student_size = models.IntegerField(blank=True, null=True)
class_subject = models.CharField(max_length=100)
class_element_name = models.CharField(max_length=100)
class_teacher_user = models.CharField(max_length=100)
class_teacher_name = models.CharField(max_length=100, blank=True, null=True)
eval_year = models.IntegerField(default=2022)
class_random_key = models.CharField(max_length=8)
eval_url = models.CharField(max_length=400)
eval_open_datetime = models.DateTimeField(null=True, blank=True)
created = models.DateTimeField(auto_now_add=True, editable=False)
last_updated = models.DateTimeField(auto_now=True, editable=False)
You can override your form to always set the eval_sent_state field to the value you want in the save method, you should remove the field from the form fields though
class ClassSchoolTeacherForm(forms.ModelForm):
class Meta:
model = ClassSchool
exclude = ['eval_sent_state']
def save(self, *args, **kwargs):
self.instance.eval_sent_state_id = 2
return super().save(*args, **kwargs)

Django CreateView Model Form not uploading File

the problem that I have is that my Model Form is not uploading a file, I had it working and after adding more code now is not working, this is what it happens: It uploads/save all the other fields except for the file, the strange thing is that if I do it from the admin site it does work. I will add that is not writing the path in the database column.
models.py
class Polizas(models.Model):
nombre = models.CharField(max_length=30, blank=True, null=True)
numero = models.CharField(max_length=30, unique=True)
aseguradora = models.CharField(max_length=20, blank=True, null=True)
carro = models.ForeignKey(
Carros, on_delete=models.CASCADE, blank=True, null=True)
inicio_poliza = models.DateField(
auto_now=False, auto_now_add=False, blank=True, null=True)
fin_poliza = models.DateField(
auto_now=False, auto_now_add=False, blank=True, null=True)
documento = models.FileField(upload_to='polizas/', blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Polizas"
ordering = ['nombre']
def __str__(self):
return self.nombre
def get_absolute_url(self):
return reverse('polizas')
forms.py
class PostPolizas(forms.ModelForm):
class Meta:
model = Polizas
fields = ('nombre', 'numero', 'aseguradora', 'carro', 'inicio_poliza',
'fin_poliza', 'documento')
widgets = {'inicio_poliza': forms.DateInput(attrs={'type': 'date'}),
'fin_poliza': forms.DateInput(attrs={'type': 'date'})
}
views.py
class PolizaCreate(LoginRequiredMixin, CreateView):
login_url = '/login/'
redirect_field_name = 'redirect_to'
form_class = PostPolizas
template_name = "add_insurance.html"
Terminal
[06/May/2020 22:32:17] "POST /insurance/add/ HTTP/1.1" 200 4557
[06/May/2020 22:32:25] "POST /insurance/add/ HTTP/1.1" 302 0
I have tried to validate the form and it is not working, this is error is happening in my other model forms that upload files, it uploads the text fields and dates but not the files.
Try adding enctype="multipart/form-data" like this:
<form enctype="multipart/form-data" method="post">
{% csrf_token%}
<table> {{form}} </table>
<input type="submit" value="Post">
</form>
in the template form.
By default forms only pass request.POST since you are uploading a file you have to pass request.FILES into the form's constructor
Follow: https://docs.djangoproject.com/en/2.2/topics/http/file-uploads/

How can I choose the field of a ForeignKey that is displayed on a ModelForm?

I have the following models:
class DirectoryDoctors (models.Model):
num = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
designation = models.CharField(
choices=design_choices, max_length=30, default='unspecified')
mobile = models.CharField(max_length=15, default='')
alternate = models.CharField(max_length=15, default='', blank=True)
email = models.CharField(max_length=50, default='', blank=True)
dob = models.DateField(null=True, blank=True)
specialty = models.ForeignKey(SpecialtyChoices, on_delete=models.DO_NOTHING,null=True)
institution = models.ForeignKey(DirectoryHospital, on_delete=models.DO_NOTHING)
def __str__(self):
st = f"{self.name}"
return st
class DhanwantriComplaint(models.Model):
num = models.AutoField(primary_key=True)
sender = models.ForeignKey(DirectoryDoctors, blank=False, null=False, on_delete=models.PROTECT)
location = models.ForeignKey(DirectoryHospital, blank=False, null=False, on_delete=models.PROTECT)
complainttype = models.ForeignKey(DhanwantriComplaintCode, blank=False, null=False, on_delete=models.PROTECT)
details = models.CharField(max_length=10000)
since = models.CharField(max_length=100, blank=True, null=True)
alertDNO = models.BooleanField(default=True)
alertZNO = models.BooleanField(default=True)
alertSNO = models.BooleanField(default=True)
ITMinformed = models.BooleanField(default=False)
ITMvisited = models.BooleanField(default=False)
prevticketid = models.CharField(max_length=100, blank=True, null=True)
raisedon = models.DateTimeField(default=timezone.now)
lastupdate = models.DateTimeField(default=timezone.now)
closed = models.BooleanField(default=False)
closedon = models.DateTimeField(blank=True, null=True)
I have the Modelform:
class DhanwantriComplaintForm(ModelForm):
class Meta:
model = DhanwantriComplaint
fields = [
'sender',
'location',
'complainttype',
'details',
'since',
'alertDNO',
'alertZNO',
'alertSNO',
'ITMinformed',
'ITMvisited',
'prevticketid',
]
widgets = {
'details': forms.Textarea(attrs={
'rows': 10,
'cols': 15
}),
'sender': forms.TextInput(),
}
And the view:
#login_required
def complaint_dhanwantri_new(request):
items = LinkSection.objects.all()
docuser = DoctorUser(request)
print(f'docuser is {docuser}. type is {type(docuser)}')
form = DhanwantriComplaintForm(
initial={
'sender': docuser,
'location': docuser.institution,
}
)
if request.method == 'POST':
print(f'Received POST: {request.POST.get}')
form = DhanwantriComplaintForm(request.POST)
if form.is_valid():
print("Form is valid")
else:
print("Form is not valid")
return render(
request, 'app/complaints/complaint.html', {
'rnd_num': randomnumber(),
'fileitems': items,
'form': form,
'docuser': docuser,
'total_docs': DirectoryDoctors.objects.count(),
'total_institutions': DirectoryHospital.objects.count()
})
And the following code in my template:
<div class="form-group row">
<label for="inputEmail3" class="col-sm-3 col-form-label">Sender: </label>
<div class="col-sm-21">
{% render_field form.sender|append_attr:"readonly:readonly" type="text" class+="form-control" %}
</div>
</div>
<div class="form-group row">
<label for="inputEmail3" class="col-sm-3 col-form-label">Location: </label>
<div class="col-sm-21">
{{ form.location|add_class:"form-control" }}
</div>
</div>
The problem is that when the form is rendered, instead of dislaying the field name of model DirectoryDoctors, the pk value is displayed as below.
How can I control what field is displayed when the form is shown?
It is because sender is a foreign key in DhanwantriComplaint model. It can only be populated with certain values (primary keys of DirectoryDoctors model). So naturally it should be a choice field (rendered as dropdown) with certain options. Django renders FK fields as dropdowns with __str__ representation of related model as display and PK as value by default. But you are forcing django here
'sender': forms.TextInput(),
to render it as text field. And because original value of this field is just a number (FK), it shows that number in field.
However if you want to user TextInput for foreign key, you have to modify your forms behavior like this
def __init__(self, initial=None, instance=None, *args, **kwargs):
if initial is None:
initial = {}
if 'sender' in initial:
initial['sender'] = initial['sender'].name
elif instance is not None:
initial['sender'] = instance.sender.name
super(PatientForm, self).__init__(initial=initial, instance=instance, *args, **kwargs)
def clean(self):
cleaned_data = super(PatientForm, self).clean()
sender = cleaned_data.pop('sender')
sender = DirectoryDoctors.objects.filter(name=sender).first()
if sender is None:
raise forms.ValidationError('Sender does not exist')
cleaned_data['sender'] = sender
return cleaned_data
The constraint on above solution is that DirectoryDoctors's name should be unique. Otherwise it can/will create a mess.

How to enter ForeginKey values in a model with CreateView

I am creating a wiki and need to put in values in the model called revision. This table has a foreigkey to wikipage.
My problem is that I am unable to insert values in the revision model.
I have tried using def form_valid(self, form) like you would when entering user, without any luck.
Models.py
class Wikipage(models.Model):
title = models.CharField(max_length=100)
date_created = models.DateTimeField('Created', auto_now_add=True)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "Wikipages"
class Revision(models.Model):
wikipage = models.ForeignKey(Wikipage, null=True, on_delete=models.CASCADE, related_name='revisions')
content = models.TextField('Content')
author = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
last_edit = models.DateTimeField('Last Edited', auto_now=True)
comment = models.TextField('Comment', blank=True)
class Meta:
verbose_name = 'Revision'
verbose_name_plural = 'Revisions'
ordering = ['-last_edit']
get_latest_by = ['last_edit']
def __str__(self):
return self.content
View.py
Class WikipageCreateView(CreateView):
template_name = 'wiki/wikipageform.html'
model = Wikipage
fields = ['title']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
The template are as simple as possible with {{ form.as_p }} and all the necessary stuff.

Django: NoReverseMatch at /shop/stickers/minion-cushion/medida-y-cantidad

I've problems when trying to access to the 2nd part of a 2 Steps form.
When clicking on a product, in a given category, for example category 'stickers' and product 'minion-cushion', users are taken to this url:
/shop/stickers/minion-cushion/medida-y-cantidad
In here they'll find a form 'StepOneForm' that only displays a tamanios (sizes in english) and cantidades (quantities in english) both as forms.ChoiceFields.
I'll capture the user's choices for this fields and save the values in the session. And then user should click on Continuar button and should be taken to this url:
/shop/stickers/minion-cushion/subir-arte
Where users will see the 2nd form "StepTwoForm" and the button to submit the form to DataBase.
However, when using this in my StepOneForm template, I get this error:
Continuar
Error:
NoReverseMatch at /shop/stickers/minion-cushion/medida-y-cantidad
Reverse for 'UploadArt' with no arguments not found. 1 pattern(s) tried: ['shop\\/(?P<c_slug>[-a-zA-Z0-9_]+)\\/(?P<product_slug>[-a-zA-Z0-9_]+)\\/subir\\-arte$']
But leaving the a tag href attribute blank lets me access this page without problems (except, obviously, I cannot access the the next page when clicking on Continue).
Continuar
Likes this:
Form in template:
<form method="post">
{% csrf_token %}
<div id="tamanios">
<legend class="text-size20 bold-font"> {{ form.tamanios.label }}</legend>
<ul class="form-items">
<li>
<span>
{{ form.tamanios.0.tag }}
{{ form.tamanios.0.choice_label }}
</span>
</li>
</ul>
</div>
Continuar
</br>
<p>Siguiente: subir imagen</p>
</form>
My urls:
app_name = 'shop'
urlpatterns = [
path('', views.allProdCat, name = 'allProdCat'),
path('<slug:c_slug>', views.allProdCat, name = 'products_by_category'),
path('<slug:c_slug>/<slug:product_slug>/medida-y-cantidad', views.StepOneView.as_view(), name='ProdCatDetail'),
path('<slug:c_slug>/<slug:product_slug>/subir-arte', views.StepTwoView.as_view(), name='UploadArt'),
]
shop/views.py
class StepOneView(FormView):
form_class = StepOneForm
template_name = 'shop/product.html'
success_url = 'shop/subir-arte'
def get_initials(self):
# pre-populate form if someone goes back and forth between forms
initial = super(StepOneView, self).get_initial()
initial['tamanios'] = self.request.session.get('tamanios', None)
initial['cantidades'] = self.request.session.get('cantidades', None)
return initial
# pre-populate form if someone goes back and forth between forms
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(
category__slug=self.kwargs['c_slug'],
slug=self.kwargs['product_slug']
)
return context
def form_valid(self, form):
# In form_valid method we can access the form data in dict format
# and will store it in django session
self.request.session['tamanios'] = form.cleaned_data.get('tamanios')
self.request.session['cantidades'] = form.cleaned_data.get('cantidades')
return HttpResponseRedirect(self.get_success_url())
# here we are going to use CreateView to save the Third step ModelForm
class StepTwoView(CreateView):
form_class = StepTwoForm
template_name = 'shop/subir-arte.html'
success_url = '/'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['product'] = Product.objects.get(
category__slug=self.kwargs['c_slug'],
slug=self.kwargs['product_slug']
)
return context
def form_valid(self, form):
form.instance.tamanios = self.request.session.get('tamanios') # get tamanios from session
form.instance.cantidades = self.request.session.get('cantidades') # get cantidades from session
del self.request.session['cantidades'] # delete cantidades value from session
del self.request.session['tamanios'] # delete tamanios value from session
self.request.session.modified = True
return super(StepTwoView, self).form_valid(form)
shop/models.py
class Category(models.Model):
name = models.CharField(max_length=250, unique=True)
slug = models.SlugField(max_length=250, unique=True)
description = models.TextField(blank=True)
image = models.ImageField(upload_to='category', blank=True)
class Meta:
ordering = ('name',)
verbose_name = 'category'
verbose_name_plural = 'categories'
def get_url(self):
return reverse('shop:products_by_category', args=[self.slug])
def __str__(self):
return '{}'.format(self.name)
class Product(models.Model):
name = models.CharField(max_length=250, unique=True)
slug = models.SlugField(max_length=250, unique=True)
description = models.TextField(blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
image = models.ImageField(upload_to='product', blank=True)
stock = models.IntegerField()
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('name',)
verbose_name = 'product'
verbose_name_plural = 'products'
def get_url(self):
return reverse('shop:ProdCatDetail', args=[self.category.slug, self.slug])
def __str__(self):
return '{}'.format(self.name)
class TamaniosCantidades(models.Model):
# usuario = models.ForeignKey(User, on_delete=models.DO_NOTHING)
producto = models.ForeignKey(Product, on_delete=models.CASCADE)
tamanios = models.CharField(max_length=10, choices=TAMANIOS)
cantidades = models.CharField(max_length=10, choices=CANTIDADES)
imagenes = models.FileField(upload_to='imagenes/', null=True, blank=True)
# imagenes = models.ImageField(upload_to='category', blank=True)
instrucciones = models.CharField(max_length=200, blank=True, null=True, default='')
uploaded_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.tamanios
shop/forms.py
class StepOneForm(forms.Form):
tamanios = forms.ChoiceField(choices=TAMANIOS, widget=forms.RadioSelect(), label='Selecciona un tamaƱo')
cantidades = forms.ChoiceField(choices=CANTIDADES, widget=forms.RadioSelect(), label='Selecciona la cantidad')
class StepTwoForm(forms.ModelForm):
instrucciones = forms.CharField(widget=forms.Textarea)
class Meta:
model = TamaniosCantidades
fields = ('imagenes', 'instrucciones')
def __init__(self, *args, **kwargs):
super(StepTwoForm, self).__init__(*args, **kwargs)
self.fields['instrucciones'].required = False
Because uploadart has arguments ( c_slug and product_slug ):
path('<slug:c_slug>/<slug:product_slug>/subir-arte',
views.StepTwoView.as_view(),
name='UploadArt'),
Your url must to inform this arguments:
<a href="{% url 'shop:UploadArt' _some_data_here_ _some_data_here_ %}"
Take a look to django url docs samples