I'm looking for guidance on creating a dynamic form in Django that changes depending on which participants are in a meeting.
The background is as follows: The app is used to track meeting scores across several teams, each with 4-8 members. Over the course of a few days a team may have 6 meetings. For each meeting the Team will elect 1 or 2 members to be meeting Participants. Each meeting requires the team to put forward members such that over the course of the training all team members have participated in one or more meetings. For each meeting all other team members are "Observers".
The challenge I'm facing is how to dynamically insert a form or form fields for each of the Observers given that the number of observers may be anything from none to 6. The idea is to allow Observers to "rate" the meeting and record this result in the Observer model.
The ScoreCreate view correctly sets the 1st participant and populates the "Other Participant" dropdown with all other team members.
HTMX in the template passes the 1st and if selected "other participant" to the observers function which is then able to sort out who the observers should be.
What I'm not able to figure out is how to generate and insert a form that is pre-populated with each of the Observers and allows for input of a score( Name of Observer, field: rating), given the number of Observers will vary depending on the number of team members and whether or not there are two participants in the meeting.
Thoughts on how I might approach this are welcome.
MODELS:
from .choices import SCORE_CHOICES, RATING_CHOICES
class Meeting(models.Model)
date = models.DateTimeField(auto_now=True)
class Team(models.Model):
# up to 8 members per team
name = model.CharField(max_length=25)
class Participant(models.Model):
name = model.CharField(max_length=25)
team = model.ForeignKey(Team, on_delete.models.CASCADE)
class Score(models.Model):
# primary is required, secondary is not required
meeting = models.ForeignKey('Meeting', on_delete.models.CASCADE)
primary_participant = models.ForeignKey('Participant', on_delete.models.CASCADE)
secondary_participant = models.ForeignKey('Participant', related_name='secondary_participant', on_delete.model.CASCADE, blank=True, null=True)
score = models.CharField(max_length=25, choices = SCORE_CHOICES)
class Observer(models.Model):
score = models.ForeignKey(Score, on_delete.models.CASCADE)
observer = models.ForeignKey(Participant, on_delete.models.CASCADE)
rating = models.CharField(max_length=1, choices=RATING_CHOICES)
VIEWS:
class ScoreCreate(LoginRequiredMixin, CreateView):
model = Score
form_class = ScoreCreateForm
template_name = 'score_create.html'
def get_initial(self):
participants = Participant.objects.filter(slug=self.kwargs['slug'])
participant_id = self.kwargs['pk']
other_participants = participants.filter(~Q(pk=self.kwargs['pk'])
def observers(request):
participant = request.GET.get('participant')
other_participant = request.GET.get('other_participant')
team = request.GET.get('team')
if other_participant:
observers = Participant.objects.filter(~Q(id=participant) & ~Q(id=other_participant) & Q(team=team))
else:
observers = Participant.objects.filter(~Q(id=participant) & Q(team=team) & Q(role='PARTICIPANT'))
return render(request, 'trial/partials/all_observers.html', context={'observers': observers})
TEMPLATE: score.html
<form method="POST" name="score_participants">
<div class="d-flex">
<div class="align-self-end g-color-primary text-uppercase g-letter-spacing-1 g-font-weight-800 g-my-10">Meeting Details
</div>
<div class="ml-auto">
<input class="btn btn-md btn-warning g-mr-10 g-mb-15 g-rounded-50" onclick="location.href='#'" type="button"
value="Cancel">
<button class="btn btn-md u-btn-primary g-mr-0 g-mb-15 g-rounded-50" type="submit">Submit</button>
</div>
</div>
<div class="d-flex g-mt-0 g-brd-top g-brd-gray-light-v3 g-bg-secondary">
<div class="g-mt-12 g-ml-10">Participant:</div>
<div>
{{form.participant|as_crispy_field}}
</div>
<div class="ml-auto g-mt-13 g-mb-12 g-mr-10 g-color-primary text-uppercase g-letter-spacing-1 g-font-weight-600">{{participant}}</div>
</div>
<div class="d-flex g-mt-0 g-brd-top g-brd-gray-light-v3">
<div class="g-mt-18 g-ml-10 g-mr-50">Meeting Number:</div>
<div class="ml-auto g-mt-13 g-mr-10">{{form.meeting_number|as_crispy_field}}</div>
<div class="d-flex g-mt-0 g-brd-top g-brd-gray-light-v3 g-bg-secondary">
<div class="g-mt-18 g-ml-10 g-mr-50">Name of other meeting participant:</div>
<div class="ml-auto g-mt-13 g-mr-10"
hx-get="{% url 'observers' %}"
hx-include="[name='score_participants']"
hx-trigger="change"
hx-target = "#observers">
{{form.other_participant|as_crispy_field}}
</div>
</div>
<div class="g-mt-0 g-brd-top g-brd-gray-light-v3">
<div id="observers"></div>
</div>
...
TEMPLATE: observer.html
{% block content %}
{% for observer in observers %}
<div class="d-flex">
<div>{{observer}}</div>
<div class="ml-auto">RATING (RADIO BUTTONS)</div>
</div>
{% endfor %}
{% endblock %}
The idea is to use the view that handles that data coming from that page you've shown going to the next page that shows the forms. So if you are just trying to show the forms on the next page you can handle the logic within your view and say if there are 4 observers then you will want to have a for loop that creates a form for each of them and appends them to a list. Then in HTMX you have for each in that list, print that form out, and you can use id's on each html object in forms.py and manage them in css.
If you are looking to save this model for later use. You essentially want to do the same thing but instead of passing those values along URL(managing the parameters in a view) you can just use them straight from the database. In fact, this would simply work in either situation with that small caveat that using more database calls will slow the site down.
Related
In my blog's model, I have a boolean flag to determine if a post is "featured" or not. Featured posts will be displayed in a different way.
To retrieve the posts I have defined two model managers as below:
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager, self).get_queryset().filter(status='published')
class FeaturedManager(models.Manager):
def get_queryset(self):
return super(FeaturedManager, self).get_queryset().filter(feature=True).order_by('-publish')[:3]
and in the views.py this is how I pass them to the template:
class PostListView(ListView):
queryset = Post.published.all()
context_object_name = 'posts'
paginate_by = 10
def get_context_data(self, *args, **kwargs):
context = super(PostListView, self).get_context_data(*args, **kwargs)
context['featured'] = Post.featured.all()
return context
Now in my template I have a section for featured posts and another section for normal posts. Normal posts are easy, but I want to display the first featured post — which is going to be the most recent one — in a separate container, and the last two in another one. There always will be the last 3 featured posts displayed.
Here's the code for template to display the first featured post:
<div class="jumbotron p-4 p-md-5 text-white rounded bg-dark">
<div class="col-md-10 px-0">
<h1 class="display-4 font-italic">The Title of Newest Featured Post</h1>
<p class="lead my-3">The Body of the Newest Featured Post</p>
</div>
</div>
My question is, how to access the first, second and third posts in featured object?
You can use the following logic
<p class="lead my-3">{{ featured | first }}</p>
OR
<p class="lead my-3">{{ featured[0] }}</p>
...
<p class="lead my-3">{{ featured[1] }}</p>
etc.
Of course you can create your own templatetags and use them whenever you need them.
Create a new folder under your project root folder with the name templatetags
Add an empty __init__.py file
Create a new file with the name second.py
Add the following code
import os
from django import template
register = template.Library()
#register.filter
def second(list):
return list[1]
On the top of your template html file where you want to use it add the following line {% load second %}
Use the following code {{ featured | second:ADD_THE_LIST }}
In case that you want to read more about the templatetags https://docs.djangoproject.com/en/3.2/howto/custom-template-tags/
Apparently the answer is to use queryset.index in the template:
featured.0 for the first, featured.1 for second and so on.
A friend of mine is trying to gather all of the data from each of his models within one view, to display on one tamplate, with the 'slug' as the URL.
He currently has a class based view that looks like this:
from itertools import chain
class ProductDetailView(DetailView):
queryset1 = BooksProduct.objects.all()
queryset2 = ClothingProduct.objects.all()
queryset3 = FurnitureProduct.objects.all()
queryset4 = DecoratingProduct.objects.all()
queryset = chain(queryset1, queryset2, queryset3, queryset4)
template_name = 'products/detail.html'
The URL looks like this:
urlpatterns =[
path('product-detail/<slug>/', ProductDetailView.as_view(), name='product-detail'),
]
The four different models all look very similar to this:
class BookProduct(models.Model):
slug = models.SlugField(max_length=40)
image = models.ImageField(upload_to='media')
title = models.CharField(max_length=150)
description = models.TextField()
short_description = models.CharField(max_length=300)
price = models.DecimalField(max_digits=5, decimal_places=2)
stock_quantity = models.PositiveIntegerField()
in_stock = models.BooleanField(default=True)
main_category = models.CharField(choices=PRODUCT_CATEGORY_CHOICES, max_length=2, default='FW')
brand = models.CharField(choices=TOY_BRAND_CHOICES, max_length=2, default='NB')
on_sale = models.BooleanField(default=False)
date_added = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return self.title
class Meta:
ordering = ['-date_added']
def get_absolute_url(self):
return reverse("products:product-detail", kwargs={
'slug': self.slug
})
At the moment, clicking on an individual product to get the product-detail.html view results in this error:
'itertools.chain' object has no attribute 'all'
How is he able to accumulate all of the products from each model into one view?
The DetailView has since been changed to ListView which has made the product details render through to the template, however as soon as you try to refresh the page, or press the back button and re click the product to view the detail page again, it disappears, until he resets the server over again.
The HTML template looks like this:
<section id="detail-view">
<div class="container">
<div class="row">
<div class="col-6">
<img src="{{ product.image.url }}" alt="{{ product.title }}">
</div>
<div class="col-6">
{% for product in object_list %}
<h1>{{ product.title }}</h1>
<strong>£{{ product.price }}</strong>
<hr class="newhr">
ADD TO BASKET
<div class="detail-desc-box">
<p>{{ product.description }}</p>
</div>
{% endfor %}
</div>
</div>
</div>
</section>
And the view now looks like this:
from itertools import chain
class ProductDetailView(ListView):
queryset1 = BooksProduct.objects.all()
queryset2 = ClothingProduct.objects.all()
queryset3 = FurnitureProduct.objects.all()
queryset4 = DecoratingProduct.objects.all()
queryset = chain(queryset1, queryset2, queryset3, queryset4)
template_name = 'products/detail.html'
Kind regards
I don't think you'll be able to use the Generic views out of the box like you're hoping. The reason is because the DetailView is specifically for showing a single model instance. You're attempting to show multiple.
What I'd recommend is to switch this to a ListView, supply an additional url keyword argument to the view to specify the slug, then do one of the following:
Use .union() to make one query to the database and deal with the constraints of that method detailed in the docs.
Refactor your models to use inheritance and make the select based on the common model.
Note:
The error you're encountering currently is because you're effectively changing the type of DetailView.queryset from QuerySet to a generator. The rest of the generic view expects certain members to be present, such as .all().
I've created an app, and on the CreateView page, the Submit button works fine to create a new S Reference. I also created an error message if the input value matches an existing Reference. I created button in the error message part and tried to link it to update the page to update these reference fields, like primary contact. I tried many options but have not got right code for the argument with pk or id to get individual record update page.
this is the url in error message.
I tried quite few pk, id options, none of them works.
'pk'=self.pk;
{'pk'=self.pk};
object.id
some code as below
models.py
class LNOrder(models.Model):
reference_number = models.CharField(max_length=15,blank=True, null=True, unique=True, error_messages={'unique':"This reference already exists."})
primary_contact = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
urls.py
urlpatterns = [
path('lfcnotifier', LNCreateView.as_view(), name='lnorder_create'),
path('lfcnotifier/<int:pk>', LNDetailView.as_view(), name='lnorder_detail'),
path('lfcnotifier/<int:pk>/update/', LNUpdateView.as_view(), name='lnorder_update'),
]
template
<div class="input-group mb-3">
<div class="input-group-prepend w-225px">
<label class="input-group-text w-100">S Reference</label>
</div>
<input name="reference_number" type="text" class="form-control" placeholder="Enter your S Reference"/>
<button class="btn btn-primary cardshadow " data-toggle="tooltip" title="Click to submit" style="width:200px;" type="submit">submit</button>
{%for field in form %}
{% for error in field.errors %}
{{ error }} Update Request
{% endfor %}
{% endfor %}
Views.py
class LNCreateView(SuccessMessageMixin,LoginRequiredMixin,CreateView):
model = LNOrder
template_name = 'lfcnotifier/lnorder_create.html'
form_class = LNOrderForm
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
I expect when users click on Update Request button, it'll open the update page to edit the individual reference.
but I got message "Could not parse the remainder: '=self.pk' from ''pk'=self.pk'".
I get slightly different messages when I try the above different options.
I would like to have the right code for the URL to update the page when the Update Request button is clicked.
Thanks,
Additional background, I only put some of template code here to save space. They are in form section. If I use the following code
Update Request
instead of
Update Request
it can open the full list page without issue. I can go to update page from full list page without issue. But I want to open update page from here directly other than have one more step.
This is all kinds of confused.
For a start, you can't use a string on the left-hand side of an expression, either in pure Python or in Django templates.
But secondly, you don't have anything called self there. What you do have would be passed from the view; however it's not clear from the code you have posted which actual view this is. It doesn't seem to be that CreateView, because you are linking to the update. But assuming it's actually the LNDetailView, and assuming that that actually is a DetailView, you have access to the current object in the template exactly as object.
So you would do:
{% url 'lnorder_update' pk=object.pk %}
However again, this makes no sense to actually do. You can't submit a form via an a. You need a <form> element with a button.
Hellow. I have a document database and flask app that gives me web-based opportunity to see the db's docs, add them and delete. Every doc has only it's number and name.
Usually I add the documents one by one, cause i have the WTForm -
class addDocForm(FlaskForm):
doc_name = StringField('Название документа', validators=[DataRequired()])
doc_number = StringField('Исходящий номер', validators=[DataRequired()])
the .html code -
<form action="" method="post" >
{{ form.hidden_tag() }}
<div class="row">
<label>{{ form.doc_name.label }}</label>
{{ form.doc_name(size=32) }}
</div>
<div class="row">
<label>{{ form.doc_number.label }}</label>
{{ form.doc_number(size=32) }}
</div>
<div class="row">
<button type="submit">Добавить</button>
</div>
</form>
and some /route logic -
#app.route('/add_doc', methods=['GET', 'POST'])
#login_required
def add_doc():
form = addDocForm()
if form.validate_on_submit():
doc = Doc(doc_name=form.doc_name)
if Doc.query.filter_by(doc_name=form.doc_name.data).first() == None:
db.session.add(doc)
db.session.commit()
So I add each document one by one filling this form and submitting it again and again. Now i've been tired. I want to save my energy by reducing number of clicking on submit button. Of course it's a joke, but the question is really about thing like this:
how can i add several copies of this 'addDocForm' on one page, fill the fields of these copies and click submit only once?
Is there any clever way to do that? I want to add for example 5-7 docs at once without the necessity to add them one by one. Let's suppose i've load the page with my form (one form) fill the fields, and than clicked '+' button and there appear another form.. fill the fields-> '+' button .. again. After all click the 'submit' button and all the data from filled fields by turns go to the data base. Is it real? any ideas? p.s. i have an idea on how to make it using clear sql + html + js, without flask-wtforms, sqalchemy and so on.. but i guess this is wrong way cause half of my app is already written using them. ) so many text, don't sure if anyone reach this point.. but still - help me, pls (((((
You could construct a MegaForm using field enclosures.
For example (untested):
from wtforms import StringField, FormField, FieldList
class AddDocForm(FlaskForm):
doc_name = StringField('Название документа', validators=[DataRequired()])
doc_number = StringField('Исходящий номер', validators=[DataRequired()])
class MegaForm(FlaskForm):
documents = FieldList(FormField(AddDocForm), min_entries=7, max_entries=7)
#app.route('/add_doc', methods=['GET', 'POST'])
#login_required
def add_doc():
form = AddDocForm()
if form.validate_on_submit():
for idx, data in enumerate(form.documents.data):
doc = Doc(doc_name=data["doc_name"])
if Doc.query.filter_by(doc_name=data["doc_name"]).first() == None:
db.session.add(doc)
db.session.commit()
I'm new to Django and I'm creating an app to create and display employee data for my company.
Currently the model, new employee form, employee table display, login/logout, all works. I am working on editing the current listings.
I have hover on row links to pass the pk (employeeid) over the url and the form is populating correctly- except the manytomanyfields are not populating, and the pk is incrementing, resulting in a duplicate entry (other than any data changes made).
I will only put in sample of the code because the model/form has 35 total fields which makes for very long code the way i did the form fields manually (to achieve a prettier format).
#view.py #SEE EDIT BELOW FOR CORRECT METHOD
#login_required
def employee_details(request, empid): #empid passed through URL/link
obj_list = Employee.objects.all()
e = Employee.objects.filter(pk=int(empid)).values()[0]
form = EmployeeForm(e)
context_instance=RequestContext(request) #I seem to always need this for {%extend "base.html" %} to work correctly
return render_to_response('employee_create.html', locals(), context_instance,)
#URLconf
(r'^employee/(?P<empid>\d+)/$', employee_details),
# snippets of employee_create.html. The same template used for create and update/edit, may be a source of problems, they do have different views- just render to same template to stay DRY, but could add an additional layer of extend for differences needed between the new and edit requests EDIT: added a 3rd layer of templates to solve this "problem". not shown in code here- easy enough to add another child template
{% extends "base.html" %}
{% block title %}New Entry{% endblock %}
{% block content %}
<div id="employeeform">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<form action="/newemp/" method="post" class="employeeform">{% csrf_token %} #SEE EDIT
<div class="left_field">
{{ form.employeeid.value }}
{{ form.currentemployee.errors }}
<label for="currentemployee" >Current Employee?</label>
{{ form.currentemployee }}<br/><br/>
{{ form.employer.errors }}
<label for="employer" class="fixedwidth">Employer:</label>
{{ form.employer }}<br/>
{{ form.last_name.errors }}
<label for="last_name" class="fixedwidth">Last Name:</label>
{{ form.last_name }}<br/>
{{ form.facility.errors }} #ManyToMany
<label for="facility" class="fixedwidth">Facility:</label>
{{ form.facility }}<br/><br/>
</div>
<div id="submit"><br/>
<input type="submit" value="Submit">
</div>
</form>
#models.py
class Employee(models.Model):
employeeid = models.AutoField(primary_key=True, verbose_name='Employee ID #')
currentemployee = models.BooleanField(null=False, blank=True, verbose_name='Current Employee?')
employer = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
facility = models.ForeignKey(Facility, null=True, blank=True)
base.html just has a header on top, a menu on the left and a big empty div where the forms, employee tables, etc all extend into.
screenshot2
So, how do I need to change my view and/or the in the template to update an entry, rather than creating a new one? (
And how do I populate the correct foriegnkeys? (the drop down boxes have the right options available, but the "-----" is selected even though the original database entry contains the right information.
Let me know if i need to include some more files/code
I have more pics too but i cant link more or insert them as a new user :< I'll just have to contribute and help out other people! :D
EDIT:
I've been working on this more and haven't gotten too far. I still can't get the drop-down fields to select the values saved in the database (SQLite3).
But the main issue I'm trying to figure out is how to save as an update, rather than a new entry. save(force_update=True) is not working with the default ModelForm save parameters.
views.py
def employee_details(request, empid):
context_instance=RequestContext(request)
obj_list = Employee.objects.all()
if request.method == 'POST':
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(request.POST, instance=e)
if form.is_valid():
form.save()
return HttpResponseRedirect('/emp_submited/')
else:
e = Employee.objects.get(pk=int(empid))
form = EmployeeForm(instance=e)
return render_to_response('employee_details.html', {'form': form}, context_instance,)
also changed template form action to "" (from /newemp/ which was the correct location for my new employee tempalte, but not the update.
Thanks to this similar question.
updating a form in djnago is simple:
steps:
1. extract the previous data of the form and populate the edit form with this these details to show to user
2. get the new data from the edit form and store it into the database
step1:
getting the previous data
views.py
def edit_user_post(request, topic_id):
if request.method == 'POST':
form = UserPostForm(request.POST)
if form.is_valid():
#let user here be foreign key for the PostTopicModel
user = User.objects.get(username = request.user.username)
#now set the user for the form like: user = user
#get the other form values and post them
#eg:topic_heading = form.cleaned_data('topic_heading')
#save the details into db
#redirect
else:
#get the current post details
post_details = UserPostModel.objcets.get(id = topic_id)
data = {'topic_heading':topic.topic_heading,'topic_detail':topic.topic_detail,'topic_link':topic.topic_link,'tags':topic.tags}
#populate the edit form with previous details:
form = UserPostForm(initial = data)
return render(request,'link_to_template',{'form':form})