Django Custom Form Field Property not Available in Template - django

I'm writing a voting system that shows the number of votes for an option in the form.
I have Vote and Option models:
class Vote(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=False, null=False)
option = models.ForeignKey(Option, on_delete=models.PROTECT, blank=False, null=False)
class Option(models.Model):
prompt = models.TextField(blank=False, null=False)
I wrote a simple custom form field to hold the current number of votes:
class VoteField(forms.BooleanField):
vote_count = 0
def __init__(self, *args, option, **kwargs):
self.vote_count = Vote.objects.filter(option=option).count()
super(VoteField, self).__init__(*args, **kwargs)
I have a form to create a list of options to vote for:
class VoteForm(forms.BaseForm):
base_fields = {}
def __init__(self, *args, options, user, **kwargs):
for options in options:
field = VoteField(option=option)
field.label = option.prompt
self.base_fields["option" + str(option.pk)] = field
super(SurveyResponseForm, self).__init__(*args, **kwargs)
And I have a template to show the voting form:
{% for field in form %}
<div class="row">
<div class="col">
{{ field }}
</div>
<div class="col">
{{ field.label }}
</div>
<div class="col">
{{ field.vote_count }}
</div>
</div>
{% endfor %}
I populated the db with an option and some responses. In the shell it works as expected:
>>> option = Option.objects.get(pk=1)
>>> vote_field = VoteField(option=option)
>>> vote_field.vote_count
3
However, my template outputs nothing where I say to print vote_count:
<div class="row">
<div class="col">
<input type="checkbox" name="option-1" id="id_option-1">
</div>
<div class="col">
First Option
</div>
<div class="col">
</div>
</div>
Am I doing something wrong here?

The "fields" that are accessed via form.<field_name> or for field in form are actually instances of BoundField. These BoundField objects are a combination of the form data and the field defined on the Form class, they are not references to the field defined on the form
To access the actual field use field.field
{{ field.field.vote_count }}

Related

Django - In a ModelForm, display data coming from a model linked with M2M relationship

I would like to add information in a form, coming from the model linked with a M2M relationship to the model I'm updating.The form works properly, but I'm not able to add any information.
Here is what I get:
Here is the expected result:
My solution: finally, I updated __str__() mthod in UserGroup model to display what I expect (but, at this stage, I lost the dynamic part and my view does not work anymore :-/)
The main model is Event and it's linked to Groups thanks to this relationship; in the form, all groups are listed and displayed with checkboxes, but I'm only able to display the groups' name, no other information.
It looks like I miss some data / information: the group name is displayed only because I use {{grp}}} (see below) but it has no attribute / filed available, even if it is initialized with a query from the group model.
I envisaged a workaround (see below) because my first tries made me consider this kind of solution, but I'm not able to reproduce what I did :-/
Any idea of what I did wrong, or any advice to manage this? Thanks in advance.
Here are related code parts.
Models:
class UserGroup(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
users = models.ManyToManyField(UserComp, verbose_name="utilisateurs", blank=True)
group_name = models.CharField("nom", max_length=100)
weight = models.IntegerField("poids", default=0)
hidden = models.BooleanField(default=False)
def __str__(self):
return self.group_name
class Event(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, verbose_name="société"
)
groups = models.ManyToManyField(UserGroup, verbose_name="groupes", blank=True)
rules = [("MAJ", "Majorité"), ("PROP", "Proportionnelle")]
event_name = models.CharField("nom", max_length=200)
event_date = models.DateField("date de l'événement")
slug = models.SlugField()
current = models.BooleanField("en cours", default=False)
quorum = models.IntegerField(default=33)
rule = models.CharField(
"mode de scrutin", max_length=5, choices=rules, default="MAJ"
)
class Meta:
verbose_name = "Evénement"
constraints = [
models.UniqueConstraint(fields=["company_id", "slug"], name="unique_event_slug")
]
def __str__(self):
return self.event_name
Form:
class EventDetail(forms.ModelForm):
groups = forms.ModelMultipleChoiceField(
label = "Liste des groupes",
queryset = None,
widget = forms.CheckboxSelectMultiple,
required = False
)
class Meta:
model = Event
fields = ['event_name', 'event_date', 'quorum', 'rule', 'groups']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
self.fields['groups'].queryset= UserGroup.objects.\
filter(company=instance.company).\
order_by('group_name')
View:
#user_passes_test(lambda u: u.is_superuser or (u.id is not None and u.usercomp.is_admin))
def adm_event_detail(request, comp_slug, evt_id=0):
'''
Manage events creation and options
'''
company = Company.get_company(request.session['comp_slug'])
# all_groups = list(UserGroup.objects.filter(company=company, hidden=False).order_by('group_name').values())
if evt_id > 0:
current_event = Event.objects.get(id=evt_id)
event_form = EventDetail(request.POST or None, instance=current_event)
else:
event_form = EventDetail(request.POST or None)
event_form.fields['groups'].queryset = UserGroup.objects.\
filter(company=company, hidden=False).\
order_by('group_name')
if request.method == 'POST':
if event_form.is_valid():
if evt_id == 0:
# Create new event
event_data = {
"company": company,
"groups": event_form.cleaned_data["groups"],
"event_name": event_form.cleaned_data["event_name"],
"event_date": event_form.cleaned_data["event_date"],
"quorum": event_form.cleaned_data["quorum"],
"rule":event_form.cleaned_data["rule"]
}
new_event = Event.create_event(event_data)
else:
new_event = event_form.save()
else:
print("****** FORMULAIRE NON VALIDE *******")
print(event_form.errors)
return render(request, "polls/adm_event_detail.html", locals())
HTML (I did not put each parts of the 'accordion' widget, I do not think they have anything to do with the problem):
{% if evt_id %}
<form action="{% url 'polls:adm_event_detail' company.comp_slug evt_id %}" method="post">
{% else %}
<form action="{% url 'polls:adm_create_event' company.comp_slug %}" method="post">
{% endif %}
{% csrf_token %}
<!-- Hidden field where the referer is identified to go back to the related page after validation -->
<input type="hidden" name="url_dest" value="{{ url_dest }}" />
<br>
<!-- Accordion -->
<div id="eventDetails" class="accordion shadow">
<div class="card">
<div class="card-header bg-white shadow-sm border-0">
<h6 class="mb-0 font-weight-bold">
Evénement
</h6>
</div>
<div class="card-body p-5">
<p>Nom : {{event_form.event_name}} </p>
<p>Date : {{event_form.event_date}} </p>
</div>
</div>
<!-- Accordion item 2 - Event's groups -->
<div class="card">
<div id="headingTwo" class="card-header bg-white shadow-sm border-0">
<h6 class="mb-0 font-weight-bold">
<a href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo" class="d-block position-relative collapsed text-dark collapsible-link py-2">
Groupes d'utilisateurs
</a>
</h6>
</div>
<div id="collapseTwo" aria-labelledby="headingTwo" data-parent="#eventDetails" class="collapse show">
<div class="card-body p-5">
<p>Sélectionnez le(s) groupe(s) d'utilisateurs participants à l'événement :</p>
<ul>
{% for grp in event_form.groups %}
<li>{{ grp }}
{{ grp.weight }}
{{ grp.hidden }}
{{ grp.nb_users }}
</li>
{% endfor %}
</ul>
<p></p>
</div>
</div>
</div>
</div> <!-- Accordion end -->
<button class="btn btn-success mt-5" type="submit">{% if evt_id %}Mettre à jour{% else %}Créer{% endif %}</button>
&nbsp &nbsp &nbsp
<a class="btn btn-secondary back_btn mt-5" href="*">Annuler</a>
<div class="row">
<div hidden>
<!-- List of groups in event -->
{{ event_form.group_list }}
</div>
</div>
</form>
Workaround
If it's not possible to achieve this directly, I thought to a workaround that would be implemented in several parts:
Create a list almost like the queryset: group_list = UserGroup.objects.filter(company=instance.company).order_by('group_name').values()
I already know I can display each group with its details and a checkbox
on client side (javascript), I manage an hidden list that would be part of the form, with the ID of each selected group. That means that the list will be dynamically updated when a box is checked on unchecked
on the POST request, read the list to update the group attribute of updated event.
I would have prefered the users actions having effect directly to the form, but I know this could work
You're accessing the form's groups field, not the model instance. The form field doesn't have any relationship to other models, it's just a field. You can access the underlying instance of the model form using form.instance.
Also note that you get a relationship manager object when querying related models. Hence, use .all to query all groups.
Try
<ul>
{% for grp in event_form.instance.groups.all %}
<li>{{ grp }}
{{ grp.weight }}
{{ grp.hidden }}
{{ grp.nb_users }}
</li>
{% endfor %}
</ul>

Django objection creation failing due to FOREIGN KEY constraint failure (IntegrityError )

My website has a comment section where user can leave comments on a product . The comments on the product page will be stored in a model called 'ProductReview'. Here is the code for the model :
class ProductReview(models.Model):
product = models.ForeignKey(Product, related_name='reviews', on_delete=models.CASCADE)
name = models.CharField(blank=True,max_length=20)
stars = models.IntegerField()
content = models.TextField(blank=True)
date_added = models.DateTimeField(auto_now_add=True)
created_by = models.OneToOneField(User, on_delete=models.CASCADE)
Now the view associated with the model are as follows Note:The entire view isnt relevant to the error. The part relevant to the saving comment is the second 'request.POST' which I have denoted with a python
comment using # :
def product(request, category_slug, product_slug):
cart = Cart(request)
product = get_object_or_404(Product, category__slug=category_slug, slug=product_slug)
if request.method == 'POST':
form = AddToCartForm(request.POST)
if form.is_valid():
quantity = form.cleaned_data['quantity']
cart.add(product_id=product.id, quantity=quantity, update_quantity=False)
messages.success(request, 'The product was added to the cart')
return redirect('product', category_slug=category_slug, product_slug=product_slug)
similar_products = list(product.category.products.exclude(id=product.id))
# this part is for saving of the user comments to productreview model
if request.method == 'POST':
stars = request.POST.get('stars', 3)
content = request.POST.get('content', '')
name = request.POST.get('name', '')
created_by = request.user
review = ProductReview.objects.create(product=product, name=name, stars=stars, content=content, created_by=created_by)
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
# this marks the end of the code relevant to saving the user comment
if len(similar_products) >= 4:
similar_products = random.sample(similar_products, 4)
user_type = 0
if request.user.is_authenticated:
user_type = UserType.objects.filter(created_by=request.user.id).values_list('user_type', flat=True)
user_type = int(user_type[0])
return render(request, 'product.html', {'product': product, 'similar_products': similar_products, 'user_type': user_type})
And finally the relevant part of the template 'product.html' which is referenced in the view
{% if request.user.is_authenticated %}
{% if user_type == 2 %}
<div class="notification space-below">
<form method="post" action=".">
{% csrf_token %}
<div class="field">
<label>Name</label>
<div class="control">
<input class="text" name="name" value="{{ request.user }}" readonly>
</div>
</div>
<div class="field">
<label>Stars</label>
<div class="control">
<div class="select">
<select name="stars">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
</div>
</div>
<div class="field">
<label>Content</label>
<div class="control">
<textarea class="textarea" name="content"></textarea>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-success">Submit</button>
</div>
</div>
</form>
</div>
{% else %}
<div>sign in with a buyer account to leave review</div>
{% endif %}
{% endif %}
Now when I try to fill the form in the product.html page and try to submit it ,I get the following error:
FOREIGN KEY constraint failed
IntegrityError at /smartwatch/apple-watch/
Can anyone tell what exactly is the issue with my code?
Pretty sure this all comes to your model, especially to this single line:
created_by = models.OneToOneField(User, on_delete=models.CASCADE)
You are using OneToOneField. This means a single user will be able to leave a single review in your entire application. You are getting integrity errors probably because this user you've selected already has a review on a different product.
If this isn't what you want, use normal ForeignKey instead.
I think your intention was to ensure that a single user can leave only one review per product. In that case, you should try setting UniqueConstraint between the product and created_by fields

How to create and start a form Select field, without it being in the model?

I have reviewed many of the questions related to this topic, in this forum and in Spanish, I have followed the steps, and it does not work for me, on something that seems so simple, at first. I have Django 2.2.6 with a form that needs the selected value of a Select field, which is not in the model, and acquires a list of values (tuples of two) created from the view.
Upon entering the form, throw this error: "__init __ () received an unexpected keyword argument 'carpetas'", in the FotoArtForm class, in the super () line.
This is my code:
models.py
class FotoArt(models.Model):
nombre = models.CharField(max_length=50)
foto = models.ImageField(upload_to='fotos/articulos/', null=True, blank=True)
class Meta:
ordering = ['nombre']
verbose_name = _('Foto Artículo')
verbose_name_plural = _('Fotos Artículos')
def __str__(self):
return self.nombre
def get_absolute_url(self):
return reverse('detalle-fotoArt', args=[str(self.id)])
views.py
class FotoArtActualizar(LoginRequiredMixin, UpdateView):
model = FotoArt
form_class = FotoArtForm
Ruta = os.path.join(MEDIA_ROOT, 'fotos', 'articulos')
def get_form_kwargs(self):
kwargs = super(FotoArtActualizar, self).get_form_kwargs()
kwargs['carpetas'] = self.get_folders_list(self.Ruta)
return kwargs
def get_folders_list(self, ruta):
foldersList = []
for _, listaSubDir, _ in os.walk(ruta):
for dName in listaSubDir:
foldersList.append((dName, dName))
return foldersList
forms.py
class FotoArtForm(forms.ModelForm):
guardar_en_carpeta = forms.ChoiceField(choices=(), required=True)
def __init__(self, *args, **kwargs):
super(FotoArtForm, self).__init__(*args, **kwargs)
self.foldersList = kwargs.pop('carpetas', None)
self.fields['guardar_en_carpeta'].choices = self.foldersList
class Meta:
model = FotoArt
fields = ['nombre', 'guardar_en_carpeta', 'foto']
fotoArt_form.html
{% extends 'catalogo/base.html' %}
{% block content %}
<h2>Crear/Actualizar Fotos Artículos</h2>
<hr/>
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.non_field_errors }}
<div class="input-group mb-3">
<div class="input-group-prepend">
<label class="input-group-text font-weight-bold" for="inputCarpeta">Guardar en carpeta</label>
</div>
{{ form.guardar_en_carpeta.errors }}
{{ form.guardar_en_carpeta }}
</div>
{{ form.foto.errors }}
<strong>{{ form.foto.label_tag }}</strong>
{{ form.foto }}
<div class="form-group row my-2">
<label for="nombreFoto" class="col-sm-2 col-form-label"><strong>Nombre</strong></label>
{{ form.nombre.errors }}
<div class="col-sm-10">
{{ form.nombre }}
</div>
<input type="submit" value="Enviar" />
</div>
</form>
{% endblock %}
Any idea why I get this error?
Perhaps, you should make another approach to pass the list of folders (choices) to the field save_in_folder of the form; instead of overwriting the get_form_kwargs () method, I could overwrite the get_context_data () method and pass it a context ['folders_list'], but then I can't control the value of the save_in_folder selector to use it in another field.
You have not defined the carpetas field in the FotoArtForm.Meta.fields but you're passing the data for that via form hence the error from the FotoArtForm initializer as carpetas is not a valid named argument.
You need to reverse the statements to pop-out the capetas named argument so that the ModelForm.__init__ is called without it:
class FotoArtForm(forms.ModelForm):
...
...
def __init__(self, *args, **kwargs):
self.foldersList = kwargs.pop('carpetas', None)
super(FotoArtForm, self).__init__(*args, **kwargs)

When refering to the object pk in the tableview i get a pk error but i still can refere to the object from the context

I have a django app where I want to show a table of user entries and users can delete/edit entries from the table by buttons. I used django-tables2 as the library to render the table.
Tables.py
class PatientTable(tables.Table):
FirstName = tables.Column(linkify=("patients:patient_detail", {"pk": tables.A("pk")}))
LastName = tables.Column(linkify=("patients:patient_detail", {"pk": tables.A("pk")}))
Telephone_no = tables.Column(linkify=("patients:patient_detail", {"pk": tables.A("pk")}))
delete = TemplateColumn('<button type ="button" class ="btn btn-danger" data-toggle="modal" data-target="#modalDelete" >Deleta</button>',extra_context={'patient': 'Patient'})
class Meta:
model = Patient
attrs = {'class': 'table table-striped table-hover'}
exclude = ("user", "Notes", "Adress")
template_name = 'django_tables2/bootstrap4.html'
Views.py
def Patients_list(request):
patients = Patient.objects.all()
table = PatientTable(patients.filter(user=request.user))
RequestConfig(request).configure(table)
return render(request, 'patients/patients_list.html',{
'table' : table,
'patients':patients,
})
here in the views I defined the patients in the context to be callable in the template,It's callable but i can't call the patients.pk, it always return a value error.
Template
{% extends 'base.html' %}
{% load render_table from django_tables2 %}
{% block content %}
<div id="content">
{% if user.is_authenticated %}
<h1> Patients list: </h1>
<br>
Add Patient
<br>
<br>
{% render_table table %}
{% else %}
<h2>please login</h2>
{% endif %}
</div>
<div class="modal fade" id="modalDelete" tabindex="-1" role="dialog" aria-labelledby="modalDelete"
aria-hidden="true">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Delete patient!</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete this patient?</p>
</div>
<div class="modal-footer">
<form method="POST" action="{% url 'patients:patient_delete' pk=patients.pk %}">
{% csrf_token %}
<input class="btn btn-danger" value="Yes" type="submit" >
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
in this template I get this error :
Reverse for 'patient_delete' with keyword arguments '{'pk': ''}' not found. 1 pattern(s) tried: ['patients/delete/(?P<pk>[0-9]+)$']
I tried
Patients.pk
pk
but it didn't work,in the template i tried making a for loop(after deleting the form ofc) to show each patient First name in a paragraph tag and it worked I also tried making a different template having for the delete form and it worked but now i want to make the delete form in a modal callable by the button.
My model:
# Patient model each patient is uniquely identified by his doctor/user
class Patient(models.Model):
FirstName = models.CharField(max_length=264)
LastName = models.CharField(max_length=264)
Adress = models.TextField(blank=True, null=True)
Telephone_no = PhoneNumberField(blank=True, null=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,related_name='patients')
birth_date = models.DateField()
# Age = models.CharField(max_length=100,blank = True ,null = True)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
Notes = models.TextField(blank=True, null=True)
def __str__(self):
return str(self.FirstName) + " " + str(self.LastName)
def get_absolute_url(self):
return reverse('patient_detail', kwargs={"pk": self.pk})
There's another related model to this one has a patient field as a ForiegnKey btw.
I tried changing the view into this as user from recommended but it's the same problem
the new view:
def Patients_list(request):
patients = Patient.objects.filter(user=request.user)
table = PatientTable(patients)
RequestConfig(request).configure(table)
return render(request, 'patients/patients_list.html',{
'table' : table,
'patients':patients,
})
The suggestion was that I couldn't get the pk from Patient.objects.all() and I needed to change it to a form with get but get didn't work so i used filter.
I think if i changed it to a CBV it should work but I don't really know how to make the queryset should i make it with defining it just like the normal CBV.
I've been stuck on this for 10 days now asking on many forums/sites so I really appreciate any help.
The only way to achieve what i wanted is to render the table manually in the template by making a for loop and iterate through all objects.

how to save many to many field using django custom forms

how can i save many courses to the student table .I want to keep my design like this.This code is not saving the many to many field(courses) through AddStudentForm.It returns an error with courses variable.If i used CharField instead of ManyToManyField in models for courses then the code works perfectly,but when i use ManyToManyField then it is not working.
it throws courses when i used form.errors .If i didn't use form.errors then it doesn't give any error neither saves the data.
how can i save many courses to the student table .I want to keep my design like this.This code is not saving the many to many field(courses) through AddStudentForm.It returns an error with courses variable.
models.py
class Course(models.Model):
title = models.CharField(max_length=250)
price = models.IntegerField(default=0)
duration = models.CharField(max_length=50)
def __str__(self):
return self.title
class Student(models.Model):
name = models.CharField(max_length=100)
courses = models.ManyToManyField(Course)
email = models.EmailField()
image = models.ImageField(upload_to='Students',blank=True)
def __str__(self):
return self.name
forms.py
class AddStudentForm(forms.ModelForm):
# courses = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Course.objects.all())
class Meta:
model = Student
fields = ['name','courses','email','image']
def __init__(self, *args, **kwargs):
super(AddStudentForm, self).__init__(*args, **kwargs)
self.fields["courses"].widget = CheckboxSelectMultiple()
self.fields["courses"].queryset = Course.objects.all()
views.py
def addstudent(request):
courses = Course.objects.all()
if request.method == 'POST':
form = AddStudentForm(request.POST,request.FILES)
if form.is_valid():
student = form.save(commit=False)
course = form.cleaned_data['courses']
student.courses = course
student.save()
# student.courses.add(course)
# student.save_m2m()
# student.courses.set(course) # this method also didn't helped me
messages.success(request, 'student with name {} added.'.format(student.name))
return redirect('students:add_student')
else:
# messages.error(request,'Error in form.Try again')
return HttpResponse(form.errors) # this block is called and returns courses
else:
form = AddStudentForm()
return render(request,'students/add_student.html',{'form':form,'courses':courses})
add_student.html
<form action="{% url 'students:add_student' %}"
method="post"
enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<h5>Full Name <span class="text-danger">*</span>
</h5>
<div class="controls">
<input type="text" name="name" class="form-
control" > </div>
</div>
<div class="form-group">
<h5>Courses<span class="text-danger">*</span>
</h5>
<div class="controls">
{% for course in courses %}
<input name ="courses" type="checkbox" id="course-
{{course.id}}" value="{{course.title}}">
<label for="course-{{course.id}}">{{course.title}}
</label>
{% endfor %} # i think the problem is here.
</div>
</div>
<div class="form-group">
<h5>Email <span class="text-danger">*</span></h5>
<div class="controls">
<input type="text" name="email" class="form-
control" required> </div>
</div>
</div>
<div class="form-group">
<h5>Image <span class="text-danger">*</span></h5>
<div class="controls">
<input type="file" name="image" class="form-control" > </div>
</div>
<div class="text-xs-right">
<button type="submit" class="btn btn-info">Add</button>
</div>
</form>
You need to save first before you can assign m2m, the system needs the primary key of the Student model before it can insert into the m2m table.
if form.is_valid():
student = form.save(commit=False)
course = form.cleaned_data['courses']
student.save()
# this will save by itself
student.courses.set(course)