Rendering list of forms in Django - python

I am trying to create a basic personality test in Django as a proof-of-concept at work. I'm new to Django (and python in general), coming at it from a C# .NET background.
I am trying to make a list of form objects (populated with information pulled from question objects stored in the database), then display them in the HTML.
This is only partly working; I can render the form attributes individually in a for loop (by calling, for example, question.pk) but nothing renders with the standard Django {{ form }} tag, and trying to submit the list of forms breaks the whole thing.
I'm pretty sure it's an issue with handling a bunch of form objects populated inside one larger html , but I'm not sure how to go about resolving it.
I've done some research into formsets, but I can't find any way to pre-populate the form items with information from the database.
Thanks in advance!
DISCQuestionForm in forms.py:
class DISCQuestionForm(forms.Form):
# create new form object from database question object
def __init__(
self,
pk,
disc_query,
dom_answer,
infl_answer,
stead_answer,
con_answer,
):
super().__init__()
self.pk = pk
self.disc_query = disc_query
self.dom_answer = dom_answer
self.infl_answer = infl_answer
self.stead_answer = stead_answer
self.con_answer = con_answer
self.disc_response = forms.DecimalField(
max_value=4,
widget=forms.NumberInput
)
disc_create method in views.py
# Create a new DISC assessment for current user
def disc_create(request, pk):
profile = User.objects.get(pk=pk)
user = int(profile.pk)
name = profile.name
rawquestionset = DISCQuestion.objects.all()
discformset = []
for item in rawquestionset:
question = DISCQuestionForm(
pk=item.pk,
disc_query=item.disc_query,
dom_answer=item.dom_answer,
infl_answer=item.infl_answer,
stead_answer=item.stead_answer,
con_answer=item.con_answer,
)
discformset.append(question)
if request.method == 'POST':
questionset = discformset[request.POST]
if questionset.is_valid():
dom = 0
infl = 0
stead = 0
con = 0
for discquestion in questionset:
if discquestion.disc_response == discquestion.dom_answer:
dom += 1
if discquestion.disc_response == discquestion.infl_answer:
infl += 1
if discquestion.disc_response == discquestion.stead_answer:
stead += 1
if discquestion.disc_response == discquestion.con_answer:
con += 1
disctest = DISCTest(
user=user,
name=name,
dom=dom,
infl=infl,
stead=stead,
con=con,
)
disctest.save()
else:
questionset = discformset
context = {
"pk": user,
"name": name,
"discquestionset": questionset
}
return render(request, "disc_create.html", context)
DISCTest and DISCQuestion models in models.py:
class DISCTest(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
name = user.name
created_on = models.DateTimeField(auto_now_add=True)
dom = models.DecimalField(max_digits=3, decimal_places=0)
infl = models.DecimalField(max_digits=3, decimal_places=0)
stead = models.DecimalField(max_digits=3, decimal_places=0)
con = models.DecimalField(max_digits=3, decimal_places=0)
class DISCQuestion(models.Model):
disc_query = models.TextField()
disc_response = models.DecimalField(max_digits=1, decimal_places=0, null=True)
dom_answer = models.DecimalField(max_digits=1, decimal_places=0)
infl_answer = models.DecimalField(max_digits=1, decimal_places=0)
stead_answer = models.DecimalField(max_digits=1, decimal_places=0)
con_answer = models.DecimalField(max_digits=1, decimal_places=0)
and finally disc_create.html in templates:
{% extends "base.html" %}
{% block page_content %}
<div class="col-md-8 offset-md-2">
<h1>Take your DISC assessment</h1>
<hr>
<h3>Insert instructions here</h3>
<hr>
<form action="/assessment/create/{{pk}}/" method="post">
{% csrf_token %}
<div>
{% for question in discquestionset %}
<p>{{question.pk}}</p>
<p>{{ question.disc_query }}</p>
{{ form }}
{% endfor %}
</div>
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}

Your DiscQuestionForm has no fields. disc_response is defined as an attribute of the form but for Django it isn't a field because it isn't added to self.fields. And form isn't defined in your template in your for loop, only question (which is the form) so {{ question }} would print the form if it had any fields.
But then the problem is that each of your question form fields would all have the same "name" attributes because they are not prefixed to make them unique.
You should read this document carefully to understand ModelForm and modelformset. Basically you need:
class DISCQuestionForm(forms.ModelForm):
class Meta:
model = DISCQuestion
def __init__(...):
...
Use the modelformset_factory to create a proper ModelFormSet that you can initialise with the request.POST when submitted.
DISCQuestionFormSet = modelformset_factory(DISCQuestionForm, form = DISCQuestionForm) # note DISCQuestionForm not needed if you don't customise anything in your form.
and in your view:
formset = DISCQuestFormSet(request.POST or None)
then in your template you can loop through the forms in the formset:
{% for form in formset %}{{ form }}{% endfor %}

Related

create a default in one Model using values from another Model in Django

I have been struggling over this for days diving down stackoverflow rabbit holes, documentation, and youtube tutorials. I'm just running in circles at this point. So...
I have two models Variables and Entry. The daily inputs for Entry are to be recordings associated with a given Variable. Ex. I want to track daily sleep as a variable. Then, using that variable, have daily data entry associated with that variable using the Entry model.
My models are below... (I suspect my schema is a mess as well, but it's generally working. This is my first time)
class Variables(models.Model):
ENTRY_TYPE_CHOICES = [
('numeric', 'enter a daily number'),
('scale', 'rate daily on a scale of 1-10'),
('binary', "enter daily, 'yes' or 'no' "),
]
id = models.IntegerField(primary_key=True)
dep_var = models.CharField(max_length=50, default='')
dv_reminder = models.CharField(max_length=50, choices=ENTRY_TYPE_CHOICES, default="numeric")
# id current user
evoler = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE)
def __str__(self):
return self.dep_var
def get_absolute_url(self):
return reverse('entry_new')
class Entry(models.Model):
id = models.AutoField(primary_key=True)
evoler = models.ForeignKey(get_user_model(), default='', on_delete=models.CASCADE) # id the active user
dep_variables = models.CharField(max_length=50, default = '')
data = models.FloatField(default=0.0)
date = models.DateField(default=datetime.date.today)
I've tried writing a function that would identify the most recent variable from a given user and to use that as the default value in the Entry.dep_variables model. That works in the local environment but causes issues when I try to migrate the database.
Views...
def daily_entry(request):
''' page to make daily entries '''
if request.method != 'POST':
# No data submitted. GET submitted. Create a blank form
form = DailyEntryForm()
else:
#POST data submitted. Process data
form = DailyEntryForm(data=request.POST)
if form.is_valid():
data = form.save(commit=False)
data.evoler = request.user
data.save()
return HttpResponseRedirect(reverse('entry_new'))
context = {'form': form}
return render(request, 'entry_new.html', context)
Forms...
class VariablesForm(forms.ModelForm):
class Meta:
model = Variables
fields = ['dep_var', 'dv_reminder' ]
labels = {'dep_var':'Dependent variable to track', 'dv_reminder': 'Type of measure'}
class DailyEntryForm(forms.ModelForm):
class Meta:
model = Entry
var = Variables.dep_var
fields = ['dep_variables', 'data', 'date']
labels = {'dep_variables': 'Dependent variable you are tracking',
'data': 'Daily entry', 'date': 'Date'}
and template.html...
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<br>
<h1>New Entry</h1>
<form class="" action="" method="post">
{% csrf_token %}
{{ form|crispy }}
<input type="submit" name="" value="Save" />
</form>
{% endblock content %}
Any and all assistance would be infinitely appreciated.

Use Key-Value Lookup for Second Model in Django Template For Loop?

I'd like to figure out a way to use a key-value lookup for a second model within a Django for loop.
This question on dictionary key-value loops in templates is on the right track, but I am using two normalized data models. There is a 'Parent' data object (EventDetail) that contains the relevant link and 'Child' data object (DateEvent) that has date-specific information with data on user actions.
I have a template tag, link_if_exists, that is being duplicated many times. Django-debug-toolbar tells me this is being duplicated 76 times right now. This 'duplicate' message is, itself, duplicated many times.
This is what I have now:
app_extras.py
#register.filter()
def link_if_exists(title):
"""Return a link if it exists"""
event_link = None
try:
event_link = EventDetail.objects.filter(title=title).values_list('link', flat=True)[0]
except IndexError:
pass
if event_link != "":
return event_link
return False
template.html
{% load 'app_extras' %}
{% for item in child_date_items %}
{% if item.title|link_if_exists %}
{{item.title}}
{% endif %}
{% endfor %}
models.py
class DateEvent(models.Model)
title = models.CharField(max_length=250)
event_date = models.DateField(default=date.today)
event_datetime = models.DateTimeField(auto_now=False)
class EventDetail(models.Model)
title = models.CharField(max_length=250)
link = models.URLField(max_length=200, default="", blank=True)
views.py
class ProblemView(TemplateView):
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(ProblemView, self).get_context_data(**kwargs)
date_today = utils.get_date_today()
child_date_items = DateEvent.objects.filter(event_date=date_today)
context['child_date_items'] = child_date_items
return context
Django-debug-toolbar output
SELECT link FROM
table WHERE title='title'
...
| duplicated 74 times
Something like this is what I am after:
Add 'event_detail' to views.py
class NewView(TemplateView):
template_name = "template.html"
def get_context_data(self, **kwargs):
context = super(NewView, self).get_context_data(**kwargs)
date_today = utils.get_date_today()
child_date_items = DateEvent.objects.filter(event_date=date_today)
context['child_date_items'] = child_date_items
event_detail = EventDetail.objects.all()
context['event_detail'] = event_detail
return context
Lookup title as key to get 'link' value in ideal_template.html
{% for item in child_date_items %}
{% if event_detail[item.title]['link'] %}
{{item.title}}
{% endfor %}
{% endfor %}
I don't know if this functionality exists so I am open to other suggestions. I am also open to computing this in views.py and iterating over a common object in the template. I understand that I could duplicate the link data and just add a link column in the DateEvent model, but that seems wasteful and I'd like to avoid that if possible. This isn't the only field I need this type of logic, so adding everything to the Child object would take up a lot of extra space in the database.
You need to do some work on your models. If there is a one-to-many relationship between them (several DateEvents for one EventDetail), then rather than duplicating title manually in both of them and then linking both again manually, you should formalize the relationship at the model level:
class EventDetail(models.Model)
title = models.CharField(max_length=250)
link = models.URLField(max_length=200, default="", blank=True)
class DateEvent(models.Model)
event_detail = models.ForeignKey(EventDetail, on_delete=models.CASCADE)
event_date = models.DateField(default=date.today)
event_datetime = models.DateTimeField(auto_now=False)
Then you can refer to the link field from any DateEvent object without any conditionals and duplicate queries (if you use select_related() properly).
So if you're working with DateEvents, you'd use:
date_events = (
DateEvent.objects
.filter(event_date=date_today)
.select_related('event_detail')
)
Then, in the template:
{% for item in child_date_items %}
{{item.event_detail.title}}
{% endfor %}

Increment a value in models.py and website behaves accordingly

I am new to Django and I've got the hang of the basics so far but I am trying to do something that the tutorials I learnt from haven't taught me and basically what I want to do is, I have a field in my models.py called delegates_num and that field is a counter for the number of delegates which sign up for a particular course. I want to be able to increment that field by 1 each time someone signs up for a particular course, the courses being [ITIL, Change Management, Management of Risk, Programme Management, PRINCE2]
So for example, if the user books an ITIL course, the counter for that course will be incremented by 1. Each course has a limit of 15 spaces so a condition somewhere which says something like:
if course.name = 'ITIL' && if delegates_num > 15
redirect user to 'course is full page'
else submit registration form and increment delegates_num by 1
I would be extremely grateful for any help, here's the code so far:
class Course(models.Model):
MY_CHOICES = (
('Open', 'Open'),
('Closed', 'Closed'),
('Fully Booked', 'Fully Booked'),
)
course_name = models.CharField(max_length=40)
course_code = models.CharField(max_length=40)
price = models.CharField(max_length=40, default='add price')
topic_details = models.TextField(max_length=200)
start_date = models.DateField('start date')
end_date = models.DateField('end date')
status = models.CharField(max_length=20, choices=MY_CHOICES)
venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
room = models.CharField(max_length=20)
delegates_num=models.IntegerField()
def add_delegate(self):
#for count, thing in enumerate(args):
self.delegates_num+=1
def __str__(self):
return self.course_name
models.py
<h1>Registration</h1>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
{% if course.course_name = 'ITIL' %}
{{ course.delegates_num|inc }}
{% if course.delegates_num > 15 %}
<meta http-equiv="refresh" content="1;url=http://example.com">
<script type="text/javascript">
window.location.href = "https://yr4-group-project-mfblack.c9users.io/sorry_full/"
</script>
{% endif %}
{% endif %}
<button type="submit" class="save btn btn-default">Save</button>
</form>
book_course.html
def book_course(request):
if request.method == "POST":
form = StudentForm(request.POST)
if form.is_valid():
student = form.save(commit=False)
student.save()
student.course.add_delegate()
return redirect('registration_complete')
else:
form = StudentForm()
return render(request, 'website/book_course.html', {'form': form})
views.py
It's probably much easier to just make a property, since I'm guessing you're not taking into account people leaving the course - or the possibility of user error.
A property that retrieves a count is usually a small database operation and has some guarrantee to be accurate
#property
def delegates_num(self)
return self.user_set.count()
I assume you have a fk to a user model or simlar..

WTForm does not entirely repopulate form data upon editing a model

Edit: Category field is now able to repopulate. Made a mistake in
retrieving the data. App.py has been edited.
I recently created an "edit post" form in which the user clicks on a link, and a few parameters are sent to a function. My function searches for the post and the wtform module attempts to repopulate the form with the data from the post targeted. It seems that the url (URLField), category (SelectField) and name (StringField) are able to repopulate, but the details (TextField) fields display a value of None. Can anyone guide me towards to right direction in terms of repopulating a flask-wtform?
Here was my attempt:
app.py
#app.route('/edit_post/<int:post_id>', methods=("GET","POST"))
#login_required
def edit_my_post(post_id):
posts = models.Post.select().where(models.Post.id == post_id)
if posts.count() == 0:
abort(404)
if post_user.id == current_user.id :
post = models.Post.select().where(models.Post.id == post_id).get()
form = forms.ProductForm(obj=post)
if form.validate_on_submit():
form.populate_obj(post)
query = models.Post.update(user = post.user.id,
name = form.name.data.strip(),
content = form.details.data.strip(),
url = form.url.data,
category = = form.category.data
).where(models.Post.id == post_id)
query.execute()
flash("Your post has been edited.", "success")
return redirect(url_for('index'))
else:
flash("Ay! Stay away from that post!","warning")
return redirect(url_for('index'))
return render_template("edit.html",form=form)
models.py
class Post(Model):
timestamp = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(
rel_model = User,
related_name = 'posts'
)
name = TextField()
content = TextField()
upvotes = IntegerField(default=0)
url = TextField()
category = TextField()
class Meta:
database = DATABASE
order_by = ('-timestamp',)
forms.py
class ProductForm(Form):
name = StringField('Content Name', validators = [DataRequired()])
url = URLField(validators=[url()])
details = TextAreaField('Content Summary', validators = [DataRequired(),Length(max=140)])
category = SelectField("Choose a category that your content fits in.", choices=CATEGORIES,coerce=int)
edit.html
{% extends "layout.html" %}
{% from 'macros.html' import render_field %}
{% block content %}
<form method="POST" action="">
{{ form.hidden_tag() }}
{% for field in form %}
{{ render_field(field) }}
{% endfor %}
<br>
<button id="submit" type="submit">Edit Post</button>
</form>
{% endblock %}
Note: CATEGORIES is a (long) array of strings.
I had fixed the issue by simply renaming the details ProductForm field to the same name as the property on the Post form. So now the form code looks like this:
class ProductForm(Form):
name = StringField('Content Name', validators = [DataRequired()])
url = URLField(validators=[url()])
content = TextAreaField('Content Summary', validators = [DataRequired(),Length(max=140)])
category = SelectField("Choose a category that your content fits in.", choices=CATEGORIES,coerce=int)
And I renamed parts of the query to retrieve the data like so:
query = models.Post.update(user = post.user.id,
name = form.name.data.strip(),
content = form.content.data.strip(),
url = form.url.data,
category = form.category.data
).where(models.Post.id == post_id)
query.execute()

How do I avoid "record already exists" form validation error using ModelForms in Django 1.6?

Following the ModelForm docs and using this model:
class ShippingLabel(models.Model):
"""Record what shipping lables were printed list"""
class Meta:
db_table = 'shipping_labels'
ordering = ('client',)
verbose_name = _('shipping label')
verbose_name_plural = _('shipping labels')
LAYOUT_LASER_2x2 = "1"
LAYOUT_TICKET = "2"
LAYOUT_LASER_1x1 = "3"
LAYOUT_CHOICES = (
( LAYOUT_LASER_1x1, _("Laser (1x1 sheet)") ),
( LAYOUT_LASER_2x2, _("Laser (2x2 sheet)") ),
( LAYOUT_TICKET, _("Ticket (3-inch wide)") ),
)
client = models.ForeignKey(Company, blank=False, null=False, unique=True, help_text=_("Which Client to ship to?"), verbose_name=_("client") )
store = models.ForeignKey(Store, blank=False, null=False, help_text=_("What store info should be used? (address, logo, phone, etc)"), verbose_name=_("store") )
packages = models.CharField(_("Packages"), max_length=30, blank=False, null=False, help_text=_("Total number of packages. One label printed per package.") )
preprinted_form = models.BooleanField(_("Pre-Printed Form"), default=False, help_text=_("Are you using pre-printed shipping label stickers?"), )
layout = models.CharField(_("Record Type"), max_length=10, blank=False, null=False, choices=LAYOUT_CHOICES, default=LAYOUT_LASER_1x1, help_text=_("Print on large labels (4 per Letter page), Laser large labels (1 per page), or ticket printer?") )
added_by = models.CharField(_("Added By"), max_length=30, blank=True, null=True, help_text=_("The User that created this order.") )
date_added = models.DateTimeField(_('Date added'), auto_now_add=True)
date_modified = models.DateTimeField(_('Date modified'), auto_now=True)
def get_absolute_url(self):
return reverse('shipping:printship', args=[str(self.id)])
def __unicode__(self):
return unicode(self.client)
I made this form template following their example (manual_label.html):
{% extends "admin/base_site.html" %}
{% load i18n %}
{% load staticfiles %}
{% block extrahead %}
{{ block.super}}
<script src="{{ STATIC_URL }}js/jquery-1.11.1.js"></script>
{% endblock %}
{% block content %}
<form id="manual_label" method="post" action="">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="generar etiquetas autoadhesivas"/>
</form>
<p>
</p>
{% endblock %}
My app urls.py:
from django.conf.urls import patterns, url
from shipping.views import printship, pcustomer, manual_label
urlpatterns = patterns('',
url(r'pcust/', pcustomer, name='pcustomer'),
url(r'mlabel/([0-9]+)/$', manual_label, name='manual_label'),
url(r'printlabel/([0-9]+)/$', printship, name='printship'),
)
My view (with lots of diagnostic logging):
#login_required()
def manual_label(request, id):
logger.debug("SHIPPING.VIEWS.manual_label")
if request.method == 'POST':
logger.debug("SHIPPING.VIEWS.manual_label: POST!")
client = get_object_or_404(Company, pk=id)
labelset = ShippingLabel.objects.filter(client=client)
if len(labelset)>0:
# Pre-existing label, update it:
logger.debug("SHIPPING.VIEWS.manual_label.POST: Update a label!")
label = labelset[0]
form = ShipLabelForm(request.POST, instance=label)
else:
# New label:
logger.debug("SHIPPING.VIEWS.manual_label.POST: Save New label!")
form = ShipLabelForm(request.POST)
if form.is_valid():
logger.debug("SHIPPING.VIEWS.manual_label.POST: form is valid")
label = form.save(commit=True)
logger.debug("SHIPPING.VIEWS.manual_label.POST: label pk: " + str(label.id) )
logger.debug("SHIPPING.VIEWS.manual_label.POST: label client name: " + str(label.client.name) )
logger.debug("SHIPPING.VIEWS.manual_label: post return")
return HttpResponseRedirect(reverse('shipping:printship', args=[str(label.id)]))
else:
logger.debug("SHIPPING.VIEWS.manual_label: GET!")
client = get_object_or_404(Company, pk=id)
labelset = ShippingLabel.objects.filter(client=client)
if len(labelset)>0:
# Pre-existing label, load it:
logger.debug("SHIPPING.VIEWS.manual_label: Pre-Existing label, load it...")
label = labelset[0]
form = ShipLabelForm(instance=label)
else:
# New label:
label = ShippingLabel(client=client,
store=request.user.employee.store,
added_by=request.user.get_username())
form = ShipLabelForm(instance=label)
logger.debug("SHIPPING.VIEWS.manual_label: get return")
return render(request, 'shipping/manual_label.html', {
'title': u"Creación de etiquetas Manual Envios",
'form': form,
})
My forms.py definition:
class ShipLabelForm(ModelForm):
class Meta:
model = ShippingLabel
localized_fields = '__all__'
fields = '__all__'
widgets = {
'added_by': HiddenInput,
'id': HiddenInput,
}
I added 'id': HiddenInput, to try and "force" record ID number to get sent out to the form, in the theory that my error occurs because without the ID number, Django will validate in "ADD" mode which will certainly trigger the "unique" flag I have on clients.
The manual_label view is called by a customer selection form, passing in the client id. The goal is to generate an ADD form if there is currently no shipping label for this client defined - which works.
And if a shipping label already exists, I pre-load the form with its data. The idea being I thought the form system would automatically do an UPDATE on the existing record.
In either case, the saved shipping label record is used to generate the shipping labels desired.
This works in the admin view (using view on site). But I wanted to give the users a simpler system. This works fine for ADDing new labels. But when I try to EDIT an existing label, I get a form validation error "client already exists".
It seemed like such an easy thing to do....
So, what am I missing tor doing wrong?
You should be using the instance argument when initializing the form, both in the POST and GET blocks.

Categories