I want to remove some fields from a form based on some values in the database. I'm not using this form to insert the data into any database, I'm going to make a csv file from this form data. Also this form is not related to any model.
forms.py
class Registration_form(forms.Form):
Applicant_Name = forms.CharField(label='Your name', max_length=100)
Applicant_age = forms.IntegerField(label ='Age of Applicant')
Applicant_email =forms.EmailField(max_length=50)
Applicant_phone = forms.CharField(max_length=10)
views.py
class Registration_View(FormView):
template_name = 'EVENTAPP/Application.html'
form_class = Registration_form
success_url = '/'
def form_valid(self, form):
Applicant_Name = form.cleaned_data['Applicant_Name'],
Applicant_age=form.cleaned_data['Applicant_age'],
Applicant_email=form.cleaned_data['Applicant_email']
Applicant_phone=form.cleaned_data['Applicant_phone']
# do some operations if form data valid
return super().form_valid(form)
models.py
class es_event(models.Model):
ev_name = models.CharField(max_length=100,verbose_name="Event Name")
ev_date = models.DateField(auto_now=False, verbose_name="Date")
ev_description = models.TextField(null=True, verbose_name="Description")
registrant_name = models.BooleanField(default=True )
registrant_age = models.BooleanField(default=False)
registrant_phone = models.BooleanField(default=False)
registrant_email = models.BooleanField(default=False)
registrant_institution = models.BooleanField(default=False)
name = models.CharField(max_length=100,null=True)
reg_open = True
slug = models.SlugField(max_length=250)
def save(self, *args, **kwargs):
self.slug = slugify(self.ev_name)
return super(es_event, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('event_detail', kwargs={'id': self.id, 'slug': self.slug })
urls.py
url(r'^events/register(?P<id>\d+)(?:/(?P<slug>[\w\d-]+))?/$', views.Registration_View.as_view(), name='event_application')
Now what I want to do is find a particular instance of es_event from the database by using the value of "id" in the URL.
Then if that instance has the attributes registrant_name,registrant_age, etc is True then the fields Applicant_Name, Applicant_age, etc will be available on the form
You can use AJAX for that. I think this is an example similar to yours, just that instead of checking if the user exists, you check if your instance has desired attributes (registrant_name, registrant_age). And when you get JSON response you show/hide fields with Javascript.
Related
I am using a class based create view to show a form to the end user. One of the fields of the form shows an initial value, which would be changed by the end user, before saving the values to the database.
The form looks like the below:
class R1Form(forms.ModelForm):
interview_date = forms.DateField(widget=DateInput())
feedback = forms.CharField(widget=CKEditorWidget())
def __init__(self, *args, **kwargs):
super(R1Form, self).__init__(*args, **kwargs)
self.initial['feedback'] = template_R1.objects.all().first().template
class Meta:
model = R1
fields = ['interview_date', 'interviewers', 'feedback', 'comment', 'recommended_level', 'progress']
widgets = {'feedback': CKEditorWidget()}
In the above form the field called 'feedback' shows an initial value from the database.
The class based, create view looks like below:
class R1CreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
model = R1
form_class = R1Form
template_name = "interviews/r1/create.html"
# This a quick way to populate fields not present in the model form, but required in the model.
def form_valid(self, form):
form.instance.candidate_id = self.kwargs['candidate']
return super().form_valid(form)
# Required because with HTMX we need to provide a url for post request, and our url also requires additional parameters
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['page'] = self.request.GET.get('page', 1)
context['candidate_pk'] = self.kwargs['candidate']
return context
def get_success_url(self):
return reverse_lazy("cvscreenings-success", kwargs={'candidate': self.kwargs['candidate'], 'message': 'Saved!'})
def test_func(self):
return True
When the form is shown to the end user, the initial value (template_R1.objects.all().first().template) is correctly displayed to the end user. However, upon editing, this edited value is not saved to the database. The database only saves the initial value, but not the edited version of the initial value.
The database model is defined as below:
class R1(models.Model):
candidate = models.ForeignKey(Candidate, on_delete=models.CASCADE)
interview_date = models.DateField(default=timezone.now)
interviewers = models.ManyToManyField(User, blank=False)
feedback = RichTextField(null=True)
comment = models.TextField(null=True, blank=True)
recommended_level = models.ForeignKey(Rank, on_delete=models.SET_NULL, null=True)
progress = models.CharField(choices=[('PROGRESS', 'Progress'),('REJECT', 'Reject')], default='PROGRESS', max_length=28, null=True)
The url pattern for the create view is defined as below:
path('r1s/create/<int:candidate>', views.R1CreateView.as_view(), name="r1s-create"),
I'm using django's CreateView to add images to a book. I pass the book's id to the class based view as a parameter in the url. Form fields such as book and language are not rendered on the template, rather they're obtained with the help of the book's id.
# views.py
class PictureCreateView(CreateView):
model = Upload
fields = "__all__"
book_id = None
def get_initial(self):
initial = super(PictureCreateView, self).get_initial()
initial = initial.copy()
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
initial['book'] = book
initial['language'] = language
initial['uploader'] = self.request.user
return initial
# set book_id so it used in the template
def get_context_data(self, **kwargs):
context = super(PictureCreateView, self).get_context_data(**kwargs)
context['book_id'] = self.book_id
return context
def form_valid(self, form, **kwargs):
print('Form is valid')
self.object = form.save()
files = [serialize(self.object)]
data = {'files': files}
response = JSONResponse(data, mimetype=response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return super(PictureCreateView, self).form_valid(form)
def form_invalid(self, form):
print('Form invalid!')
print(form.errors)
data = json.dumps(form.errors)
return HttpResponse(content=data, status=400, content_type='application/json')
# models.py
class Upload(models.Model):
image = models.ImageField(upload_to=get_upload_path, help_text='Image to process')
uploader = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, related_name='uploader')
language = models.ForeignKey(Language, models.CASCADE)
book = models.ForeignKey(Book, models.CASCADE)
The problem is that I get an error saying the form is invalid, and the fields uploader, book and language are required. How do I resolve this?
The initial data is used to display the defaults when the form is initially displayed. It isn't used when those values are missing from the submitted form data. If fields like book and uploader are set from the URL or logged-in user, then you should leave them out of the form completely, instead of setting them in the initial data. You can then set the values on the instance in the form_valid method before the form is saved.
from django.contrib.auth.mixins import LoginRequiredMixin
class PictureCreateView(LoginRequiredMixin, CreateView):
model = Upload
fields = ['other_field1', 'other_field2', ...] # leave out book, language and uploader
def form_valid(self, form):
self.book_id = self.kwargs['book_id']
book = Book.objects.get(id=self.book_id)
form.instance.book = book
form.instance.language = ????
form.instance.uploader = self.request.user
return super(
The LoginRequiredMixin makes sure that only logged-in users can access the view.
You may want to use get_object_or_404 to handle the case where book_id refers to a book that does not exist.
One thought, initial doesn't fill the model for submission. You need to do that in init
def __init__(self):
super(PictureCreateView, self).__init__()
self.fields['book'] = self.initial['book']
self.fields['uploader'] = self.initial['uploader']
self.fields['language'] = self.initial['book']
Or, if you don't want to set the fields, make sure they are optional in your original model:
class Upload(models.Model):
uploader = models.ForeignKey('uploader', on_delete=models.CASCADE, null=True, blank=True)
book = models.ForeignKey('book', on_delete=models.CASCADE, null=True, blank=True)
language = models.ForeignKey('language', on_delete=models.CASCADE, null=True, blank=True)
I want to change the rendered field shown in a model form choicefield, based on some user selected feature, which is language in my case.
I've got a two models. Of the two, the 'Vastausvaihtoehto' model saves an answer in both english and finnish, saving it to the database. It also returns the finnish answer by default, because that's how I've defined the unicode function:
Model
class Vastausvaihtoehto(models.Model):
...
vastaus_fi = models.CharField(
verbose_name=_(u'Vastaus'),
max_length=256,
null=True,
blank=True,
)
vastaus_en = models.CharField(
verbose_name=_(u'Vastaus_en'),
max_length=256,
null=True,
blank=True,
)
...
def __unicode__(self):
return u'%s' % (self.vastaus_fi)
class Valinta(models.Model):
organisaatio = models.ForeignKey(
Organisaatio,
related_name=_(u'valinta'),
null=True,
blank=True,
on_delete=models.CASCADE,
)
kysymys = models.ForeignKey(
Kysymysvaihtoehto,
related_name=_(u'valinta'),
null=True,
blank=True,
)
vastausvaihtoehto = models.ForeignKey(
Vastausvaihtoehto,
related_name=_(u'valinta'),
null=True,
blank=True,
)
def __unicode__(self):
return u'%s' % (self.kysymys)
I also have a ModelForm, that I use to select the correct choices
Form
class ValintaForm(ModelForm):
class Meta:
model = Valinta
fields = '__all__'
widgets = {
'organisaatio':forms.HiddenInput(),
'kysymys':forms.HiddenInput(),
'vastausvaihtoehto':forms.RadioSelect(),
}
And here's my view:
View
class kysymys(View):
template_name = 'mytemplate.html'
success_url = 'something'
def get(self, request, pk, question_id, *args, **kwargs):
kysymys = Kysymysvaihtoehto.objects.get(kysymys_id=int(question_id))
vastausvaihtoehdot = Vastausvaihtoehto.objects.filter(kysymysvaihtoehto=kysymys)
if request.LANGUAGE_CODE == 'fi':
# What do I put here?
else:
# What do I put in here?
form = ValintaForm()
form.fields['vastausvaihtoehto'].queryset = vastausvaihtoehdot
form.fields['vastausvaihtoehto'].empty_label = None
return render(request, self.template_name, {
'form':form,
'kysymys':kysymys,
"pk":pk,
"question_id":question_id,
})
I've tried to query just some certain values using values and values_list, and set them as the ModelForm queryset:
#Like so:
answers_en = Vastausvaihtoehto.objects.filter(kysymysvaihtoehto=kysymys).values_list('pk','vastaus_en')
form.fields['vastausvaihtoehto'].queryset = answers_en
But that does not render the form correctly. Should I add a helper method to the 'Vastausvaihtoehto' model, which returns the english name when called?
I know it's possible to circumvent this by just not using ModelForms, but is there a way to do this while using a ModelForm?
Define your ModelForm with an __init__ method which will accept language and question_id as keyword arguments.
class ValintaForm(ModelForm):
class Meta:
model = Valinta
fields = '__all__'
widgets = {
'organisaatio':forms.HiddenInput(),
'kysymys':forms.HiddenInput(),
'vastausvaihtoehto':forms.RadioSelect(),
}
def __init__(self, *args, **kwargs):
language = kwargs.pop('language', None)
question_id = kwargs.pop('question_id')
super(ValintaForm, self).__init__(*args, **kwargs)
if language == "fi":
kysymys = Kysymysvaihtoehto.objects.get(kysymys_id=int(question_id))
vastausvaihtoehdot = Vastausvaihtoehto.objects.filter(kysymysvaihtoehto=kysymys)
self.fields['vastausvaihtoehto'].queryset = vastausvaihtoehdot
else:
# put your other conditions here
pass
In your views, when you initialize your form, pass the keyword arguments
form = ValintaForm(language=request.LANGUAGE_CODE, question_id=question_id)
Or if you think it is better, you can pass the whole queryset to the forms.
def __init__(self, *args, **kwargs):
qs = kwargs.pop('qs')
super(ValintaForm, self).__init__(*args, **kwargs)
self.fields['vastausvaihtoehto'].queryset = qs
Pass the query set when you initialize form
form = ValintaForm(qs=vastausvaihtoehdot)
Django 1.10
I'm trying to add data to a form programmatically.
class Wiki(models.Model):
related_model = models.CharField(max_length=100, blank=False, null=False, default="")
related_object_id = models.CharField(max_length=100, blank=False, null=False, default="")
article = models.TextField(blank=False, null=False, default="")
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def post(self, request, *args, **kwargs):
related_model = kwargs.get('model')
related_object_id = kwargs.get('pk')
form = self.get_form()
form.data._mutable = True
form.data['related_model'] = related_model
form.data['related_object_id'] = related_object_id
form.data._mutable = False
return super(WikiCreate, self).post(request, *args, **kwargs)
In the post method of the superclass I place a breakpoint:
class ProcessFormView(View):
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid(): # breakpoint
return self.form_valid(form)
else:
return self.form_invalid(form)
Well, what can I see at the breakpoint.
form.data =
Then step in the debugger. form.is_valid() returns true. So, now I can see that: 1) _errors is empty; 2) cleaned_data = {'article': "Some text I've just input."}.
Well, 'related_model' and 'related_object_id' have not appeared in the cleaned data.
Could you help me understand why data from these fields are not saved?
This is because you only have the fields:
fields = ['article']
So there are no other fields on your form other than article. Try adding the other two fields to the fields array. If you want them to be there, but not visible you need to create a custom form and set them to have the hidden widget
This isn't the way to add data to a form submission. You should be adding it to the model instance, not the form, once that has been created in the form_valid method. You shouldn't be overriding post at all.
class WikiCreate(CreateView):
model = Wiki
fields = ['article']
def form_valid(self, form):
related_model = self.kwargs.get('model')
related_object_id = self.kwargs.get('pk')
item = form.save(commit=False)
item.related_model = related_model
item.object_id = related_object_id
item.save()
return redirect(self.get_success_url())
I can't work out how to get the correct instance for the form_valid part of my generic view.
I am trying to allow a user to post on their project wall(bit like Facebook). I need the post to be related to an individual project(a user can have more than one project). Should the instance be a pk or the project title? Any example code or help would be very appreciated! I struggle understanding how when you create a new post, it knows which project to associate itself with.
views
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
newpost = form.save(commit=False)
form.instance.user = self.request.user
newpost.save()
self.object = newpost
return super(NewPost, self).form_valid(form)
def get_success_url(self):
project_username = self.request.user.username
project_slug = self.object.slug
return reverse('user_project', kwargs={'username':project_username, 'slug': project_slug})
models
class UserProject(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
project_overview = models.CharField(max_length=1000)
project_picture = models.ImageField(upload_to='project_images', blank=True)
date_created = models.DateTimeField(auto_now_add=True)
project_views = models.IntegerField(default=0)
project_likes = models.IntegerField(default=0)
project_followers = models.IntegerField(default=0)
slug = models.SlugField(max_length=100, unique=True) #should this be unique or not?
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(UserProject, self).save(*args, **kwargs)
def __unicode__(self):
return self.title
class ProjectPost(models.Model):
project = models.ForeignKey(UserProject)
title = models.CharField(max_length=100)
post_overview = models.CharField(max_length=1000)
date_created = models.DateTimeField(auto_now_add=True)
post_views = models.IntegerField(default=0)
post_likes = models.IntegerField(default=0)
forms
#form to add project details
class UserProjectForm(forms.ModelForm):
class Meta:
model = UserProject
fields = ('title', 'project_picture', 'project_overview')
#form to create a post
class ProjectPostForm(forms.ModelForm):
class Meta:
model = ProjectPost
fields = ('title', 'post_overview')
Ok, in that case, I would recommend a URL something like
url(r'^(?P<pk>\d+)/post/add/$', views.NewPostCreateView.as_view(), name='...'),
and then a view like
class NewPost(CreateView):
model = ProjectPost
form_class = ProjectPostForm
template_name = 'howdidu/new_post.html'
def form_valid(self, form):
self.object = form.save(commit=False)
# Find project by using the 'pk' in the URL
project = get_object_or_404(UserProject, pk=self.kwargs['pk'])
# Then just set the project on the newPost and save()
self.object.project = project
self.object.save()
return super(NewPost, self).form_valid(form)
def get_success_url(self):
# Unchanged ...
I see in your code that you were trying to do something with the user but I don't understand why your Post does not have a user field (you may want to add a created_by) and the UserProject should already have a user set.
I am also assuming the user got to the his/her project first, so you know by definition that the project he is adding a post to is his. If that is not the case, then just change the logic to get the UserProject through a regular query. e.g. maybe with `UserProject.objects.get(user = self.request.user) if there is one project per user (again, just as an example).
Anyway, I am making some assumptions here, but hopefully the main question was how to set the project on the newPost and that is answered in my example.