Django: submit only one form created with for loop - python

In my code I'm using a class CreateView with a ListView. I'm also using a for loop to show all the possible dates available (that are created in the StaffDuty models). My user should be able to just book a single date.
My problem is that I'm not able to save a single appointment, I have to compile all the form showed in my list to be able to submit. How can I solve this?
models.py
class UserAppointment(models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
staff = models.ForeignKey(StaffDuty, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE)
event_name = models.CharField(max_length=255)
date_appointment = models.DateField(null=True)
def __str__(self):
return self.event.name | str(self.staff.date_work)
def get_absolute_url(self):
return reverse('home')
views.py
class UserAppointmentAddView(CreateView):
model = UserAppointment
form_class = UserAppointmentForm
template_name = "reservation.html"
def form_valid(self, form):
form.instance.user = self.request.user.userinformation
def get_context_data(self, **kwargs):
kwargs['object_list'] = StaffDuty.objects.order_by('id')
return super(UserAppointmentAddView, self).get_context_data(**kwargs)
html
<div class="container">
<form method="post">
{% csrf_token %}
{% for appointment in object_list %}
<span>{{ form.staff }}</span>
<span>{{ form.event }}</span>
<span>{{ form.morning_hour }}</span>
<span>{{ form.afternoon_hour }}</span>
<div class="primary-btn">
<input type="submit" value="submit" class="btn btn-primary">
</div>
</div>
{% endfor %}

If I understand right, you are generating the same form for each appointment/ model instance, which won't work. What gets POSTed back does not identify which appointment object it refers to. That could be fixed with a hidden object_id field ... which is part of what model formsets do for you. Or you might put it into the value of your multiple submit buttons and pull it out of request.POST if you are happy to handle validation yourself.
The pure-Django solution is to generate a model formset. When it is submitted you would process the form that has changed and ignore the rest. (It will in fact give you lists of changed and unchanged objects).
The other approach would be using JavaScript to populate one form (possibly with hidden fields) when the user clicks on values in a table. Or no form in the template, just POST from JavaScript and validate what comes back through a form (or otherwise)
Formsets and model formsets look fearsomely complicated the first time you try to use them. They aren't really, but the first one you code may present you with a bit of a steep learning curve. It's easier if you can find something working that somebody else has already written, and adapt it.

Related

What would be the best approch to display a commenting form in the ListView for each blog post?

I currently have fully functional commenting form in my blog post view that I want to display in the ListView. Sort of like linkedin has under every list item, if you have noticed, i think facebook has the same thing.
Is there a shortcut to achieve this?
I supposed you can combine a ListView with a FormMixin (https://docs.djangoproject.com/fr/4.1/ref/class-based-views/mixins-editing/#django.views.generic.edit.ModelFormMixin)
In each item of list, you create your form html and checking if form exist and if form instance corresponds to current list view for displaying errors and data in case of invalid form sent.
class MyPostList(FormMixin, ListView);
model = Post
form = CommentAddForm
template...
class CommentAddForm(ModelForm):
class Meta:
model = Comment
fields = ('post_id', 'txt'...)
{% for post in post_list %}
{{post}}
<form>
{% if form.data.post_id == post.pk %}{{form.errors}}{% endif %}
<input type="hidden" name="post_id" value="{{post.pk}}" />
</form>
{% endfor %}

(Django) Displaying multiple generated forms (unknown quantity) in the view, and retrieving their results?

I'm currently attempting to display a set of generated forms into the view. So take the following view, for example (as part of a TemplateView class):
def get(self, request, id):
df = create_dataframe(User.objects.get(user=request.user.username))
context = generate_context_forms(df=df, user=request.user.username)
return authentication_check(
request=request, template_name=self.template_name, context=context
)
In essence, the create_dataframe function generates some relevant calculations using some user-inputted data, and that data designates how many forms I will be generating for the user. The generate_context_forms function utilizes that DataFrame to generate the context for the user. The authentication_check is a basic function that acts like render but with some extra things. Typically, a context will look like so:
context = {
'general': GeneralForm,
'specific1': SpecificForm,
'specific2': SpecificForm,
...
}
To n amounts of SpecificForm. What I am trying to do is display all of the specific forms to the user without causing loss of control. All the forms will be submitted by a single "submit" button, and after submission, I must gather all of the data they individually submitted. With the context above, I do not know how I would iterate through all the specific forms in the context.
Another attempt I did was to simply add the specific forms to a list:
context = {
'general': GeneralForm,
'specific': [SpecificForm, SpecificForm, ...],
}
But it seems that when I loop through them within my view:
{% for form in specific %}
<div class="ui fluid card">
<div class="content">
<a class="header">{{ form.instance.prompt }}</a>
</div>
<div class="extra content">
{{ form|crispy }}
</div>
</div>
{% endfor %}
What ends up happening is that the radio-button of each generated form interacts with the other. In other words, within all of the 'specific form' there is a selection of Yes/No radio buttons for a question, but it seems that all of those radio buttons unselect each other (independent of the form). This is also a solution that I am unsure how to pull the user submission data from afterward.
Any help would be appreciated on this topic, as it's something I've been a bit stuck on for the last few days, and I can't find any relevant information online for "generated forms."
Perhaps you could try to implement a solution using formsets or model-formsets for your 'SpecificForm' ?
So I figured out what I needed to do after all. In the forms.py file, I had the following:
class FeedbackSpecificForm(forms.ModelForm):
reaction = forms.CharField(
label="What is your reaction to this feedback?",
required=True,
widget=forms.Textarea,
)
awareness = forms.ChoiceField(
label="Were you aware that you used this cue to make your rating?",
required=True,
choices=[("yes", "Yes"), ("no", "No")],
widget=forms.RadioSelect,
)
why = forms.CharField(
label="Why do you think you utilized this cue to make your rating?",
required=True,
widget=forms.Textarea,
)
class Meta:
model = FeedbackSpecific
fields = ["reaction", "awareness", "why"]
What I needed to do was simply create a model form set (in the forms.py):
from django.forms import modelformset_factory
FeedbackSpecificFormSet = modelformset_factory(
FeedbackSpecific, form=FeedbackSpecificForm
)
Which then allowed me, in the generate_context_forms() function, to create a list of FeedbackSpecific:
specifics = []
for prompt in prompts:
prompt = f ' ... some stuff here ...'
specific = FeedbackSpecific(feedback=feedback, prompt=prompt)
specific.save()
specifics.append(specific)
And then generate my Formset:
specificFormSet = FeedbackSpecificFormSet(queryset=specifics)
Which I returned to my view and sent it into the context:
generalForm, specificFormSet = generate_user_form(request.user, feedback)
return render(
request,
self.template_name,
{"generalForm": generalForm, "specificFormSet": specificFormSet},
)
Within the view, I simply iterated through the specificFormSet:
{% for form in specificFormSet %}
<div class="ui fluid card">
<div class="content">
<a class="header">{{ form.instance.prompt }}</a>
</div>
<div class="extra content">
{{ form|crispy }}
</div>
</div>
{% endfor %}
It was easier than I thought it would've been. The documentation in the ModelForm is frankly a bit confusing. Hopefully this helps someone out.

Creating Forms dynamically from a model

I'm learning Django as I go right now, but I'm trying to do something more dynamically.
Basically I've got multiple models defined as so:
class Group(models.Model):
name = models.CharField(max_length=255)
type = models.CharField(max_length=255) # Ignore this, is used somewhere else
class User(models.Model):
name = models.CharField(max_length=255)
group = models.ForeignKey(Group, verbose_name='group', on_delete=models.CASCADE)
(there are more models than these two)
I feel like writing a different view for each of these models isn't really a Django way to do it, but I'm struggling to find a different way. Basically I want to automatically create forms depending on what fields the model has.
So on /department/ there should be an text input for name and type.
But on /user/ there should be an text input for name and an selection for group.
All that rather in the same template. If possible ofcourse.
If it isn't possible, what's the best way to do this? Since creating a different template for each model doesn't seem right.
EDIT:
I've now got my CreateView working (very basic still). Now I want to create a ListView in a similar fashion. I've seen this issue, Iterate over model instance field names and values in template but it wasn't able to help me out...
forms.py
class ModelCreate(CreateView):
fields = '__all__'
template_name = 'polls/models/model_create.html'
def get_form(self, form_class=None):
try:
self.model = apps.get_model('polls', self.kwargs['model'])
except LookupError:
raise Http404('%s is not a valid model.' % self.kwargs['model'])
return super(ModelCreate, self).get_form(form_class)
model_create.html
{% extends 'polls/core.html' %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit">
</form>
{% endblock %}
Would it be possible to create a ListView in a similar fashion? So that I get field names and values dynamically (bit like {{ form }})

Stuck with django form validation

I'm trying to get validation running on a django form used to retrieve a list of objects in a ListView View. Despite having read django docs and many other questions here, I can't find out what's wrong in this simple test code:
form.html
<form action="list.html" method="get">
{{ form }}
<input type="submit" value="Submit">
</form>
list.html
<ul>
{% for area in object_list %}
<li>{{ area.name }}</li>
{% endfor %}
</ul>
forms.py
from django import forms
class SearchArea(forms.Form):
area = forms.CharField(label='Area code', max_length=6)
def clean_area(self):
area = self.cleaned_data['area'].upper()
if '2' in area:
raise forms.ValidationError("Error!")
return area
views.py
class HomePageView(FormView):
template_name = 'form.html'
form_class = SearchArea
class AreaListView(ListView):
template_name = 'list.html'
model = AreaCentral
def get_queryset(self):
q = self.request.GET.get('area')
return AreaCentral.objects.filter(area__istartswith=q)
When I try to submit something like "2e" I would expect a validation error, instead the form is submitted. Moreover I can see in the GET parameters that 'area' is not even converted to uppercase ('2E' instead of '2e').
The default a FormView will only process the form on POST; the GET is for initially displaying the empty form. So you need to use method="post" in your template form element.
Your action attribute is also suspect; it needs to point to the URL of the form view. If that actually is the URL, note it's not usual to use extensions like ".html" in Django URLs, and I would recommend not doing so.

Django user authentication and rendering content created by that specific user?

I am creating a very simple to-do list application. Each user should have an associated to-do list page with basic CRUD functionality. That means User A should have different data than User B.
As of right now there is no distinction about who owns the to-do list. Anyone who is logged in can add, remove, display, delete tasks.
I have a gut feeling that I might need an extra something in my model and my template. I should mention that I use Pinax 0.9a2. If it does what I need it to do, I would prefer to use that solution instead.
Here's my models.py
class Task(models.Model):
name = models.CharField(max_length=100)
added_at = models.DateTimeField(auto_now_add=True)
last_update = models.DateTimeField(auto_now=True)
def __unicode__(self):
return self.name
Here's one of my forms in views.py
def task_create(request):
return create_object(request,
model=Task,
template_name='task_create.html',
post_save_redirect=reverse("todo_list")
)
Here's 2 of my templates:
To-Do Create
<form action="" method="post">{% csrf_token %}
{{ form.name }}
<button type="submit" class="btn primary">Create →</button>
</form>
To-Do List
{% if task_list %}
<p>Create a task</p>
<ul>
{% for task in task_list %}
<li>{{ task.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>No tasks to display, click to create new.</p>
{% endif %}
So you just want to add access control to it?
Add ForeignKey to auth.User to your ToDos model
Rewrite your list and create views to make their work manually (you can achieve your goal with new class based views but you need to understand how do they work first)
Add filter for queryset in list view
Provide commit=False to your form's save(), set up user for retrieved object and save it manually
Code:
class Task(models.Model):
user = models.ForeignKey('auth.User')
name = models.CharField(max_length=100)
added_at = models.DateTimeField(auto_now_add=True)
last_update = models.DateTimeField(auto_now=True)
class TaskForm(forms.ModelForm):
class Meta:
model = Task
exclude = ['user', ]
def task_create(request):
form = TaskForm(data=request.POST or None)
if request.method == 'POST' and form.is_valid():
task = form.save(commit=False)
task.user = request.user
task.save()
return reverse("todo_list")
return render(request,
'task_create.html',
{'form': form}
)
Also, add filtering by request.user in list view and I'd recommend #login_required decorator to avoid adding tasks by non-authorized users.

Categories

Resources