问题
I've started to create a form. Basically the form is a "test" of twenty words. The form consists of twenty text fields which I want to contain the definition of a word. The user would input the word. Once complete, the form should validate the data and mark what is correct and what isn't. I've done plenty of modelforms in django, but this one is different. All the data in this form has to be passed through as context.
views.py
def get_test(request, username='default'):
template_name = 'main/test.html'
if request.method == 'POST':
pass
else:
lang = Language(config('USER'), config('PASS'))
streakinfo = lang.get_streak_info()
uniquewords = lang.get_unique_words()
testwords = get_test_words(uniquewords)
wordsdict = get_word_dict(testwords)
form = TestForm()
context = {
'testwords': testwords, # list of random unique test words
'wordsdict': wordsdict, # dict of words + definitions {word: {pronounciation, definition}}
'form': form,
}
return render(request, template_name, context)
forms.py
class TestForm(forms.Form):
word_1 = forms.CharField(label='1', max_length=100)
word_2 = forms.CharField(label='2', max_length=100)
word_3 = forms.CharField(label='3', max_length=100)
word_4 = forms.CharField(label='4', max_length=100)
word_5 = forms.CharField(label='5', max_length=100)
word_6 = forms.CharField(label='6', max_length=100)
word_7 = forms.CharField(label='7', max_length=100)
word_8 = forms.CharField(label='8', max_length=100)
word_9 = forms.CharField(label='9', max_length=100)
word_10 = forms.CharField(label='10', max_length=100)
word_11 = forms.CharField(label='11', max_length=100)
word_12 = forms.CharField(label='12', max_length=100)
word_13 = forms.CharField(label='13', max_length=100)
word_14 = forms.CharField(label='14', max_length=100)
word_15 = forms.CharField(label='15', max_length=100)
word_16 = forms.CharField(label='16', max_length=100)
word_17 = forms.CharField(label='17', max_length=100)
word_18 = forms.CharField(label='18', max_length=100)
word_19 = forms.CharField(label='19', max_length=100)
word_20 = forms.CharField(label='20', max_length=100)
I mean, it's simple enough to go through and render each field manually, but what I don't know and have never done is this without a model. For instance, I want to build up a table, col 1 has the definition (I don't actually need the label=## because again, I am passing the data through as context), col 2 has the field. How do I tie the post data together such that when I post the results, col 2 is most assuredly checked against col 1? In a nutshell, how can I manually render and validate a form and keep all the data lined up? I apologize ahead of time if this is to broad of a question.
Update:
I was able to get the test data into the form and render the fields with the following (by hacking away at the forms.Form inheritance):
class TestForm(forms.Form):
"""
Student test form
"""
def __init__(self, testdict, *args, **kwargs):
super().__init__(*args, **kwargs)
self.testdict = {} if testdict is None else testdict
d = self.testdict
for word in d:
answer = word
for key in d[word]:
value = str(d[word][key])
if key == 'id':
field_name = value
if key == 'definition':
question = value
self.fields[field_name] = forms.CharField(label=question, max_length=100)
Still need help though.
回答1:
Yes, you can override the clean mehtod and implement your validations on them like this:
You have wrtten all the fields with form class and then using get you actually get what is being entered in those fields by their name HTML parameter and then you manipulate the data by their variables and if they don't match to what exactly you desire then raise a Validationerror. Then you're creating a context of all that data from those fields into a dict and setting a variable to it which at the end is returned.
forms.py
class TestForm(forms.Form):
word_1 = forms.CharField(label='1', max_length=100)
word_2 = forms.CharField(label='2', max_length=100)
word_3 = forms.CharField(label='3', max_length=100)
word_4 = forms.CharField(label='4', max_length=100)
word_5 = forms.CharField(label='5', max_length=100)
word_6 = forms.CharField(label='6', max_length=100)
word_7 = forms.CharField(label='7', max_length=100)
word_8 = forms.CharField(label='8', max_length=100)
word_9 = forms.CharField(label='9', max_length=100)
word_10 = forms.CharField(label='10', max_length=100)
word_11 = forms.CharField(label='11', max_length=100)
word_12 = forms.CharField(label='12', max_length=100)
word_13 = forms.CharField(label='13', max_length=100)
word_14 = forms.CharField(label='14', max_length=100)
word_15 = forms.CharField(label='15', max_length=100)
word_16 = forms.CharField(label='16', max_length=100)
word_17 = forms.CharField(label='17', max_length=100)
word_18 = forms.CharField(label='18', max_length=100)
word_19 = forms.CharField(label='19', max_length=100)
word_20 = forms.CharField(label='20', max_length=100)
def clean(self):
word_1 = self.cleaned_data.get("word_1")
# |
# | write the clean method of all fields
# |
# -----
# ---
# -
word_20 = self.cleaned_data.get("word_20")
if word_1 and word_2 and word_7 and word_15 != something:
raise forms.ValidationError("Something Fishy")
# i combined few of the word fields but you check all the fields separately also and implement your validation.
words_context = {
'word_1':word_1
# | <--write all the context of corresponding fields
# |
'word_20':word_20
}
return words_context
Views.py
def get_test(request, username='default'):
template_name = 'main/test.html'
form = TestForm()
if request.method == 'POST':
if form.is_valid():
word_1 = self.cleaned_data.get("word_1")
# |
# | write the clean method of all fields
# |
# -----
# ---
# -
word_20 = self.cleaned_data.get("word_20")
newtest = Test(word_1=word_1,....word_20=word_20)
newtest.save()
return redirect('whereever you want to redirect')
else:
lang = Language(config('USER'), config('PASS'))
streakinfo = lang.get_streak_info()
uniquewords = lang.get_unique_words()
testwords = get_test_words(uniquewords)
wordsdict = get_word_dict(testwords)
form = TestForm()
context = {
'testwords': testwords, # list of random unique test words
'wordsdict': wordsdict, # dict of words + definitions {word: {pronounciation, definition}}
'form': form,
}
return render(request, template_name, context)
回答2:
I accomplished this two ways: one of which includes a file write and the other a model write. Since writing to models is obviously faster, I will showcase that:
In the view I think it's pretty straight forward. Here the start of this is that I am passing in the word dictionary to the form when I instantiate the form on a GET request form = TestForm(wordsdict). POST request data is not ever actually stored, just used to validate. So when I POST, I simply send in the POST data as normal. wordsdict is a dictionary composed of a {answer: [question, id]}
views.py
def language_test(request, username='johndoe', password=None):
lang= Language(config('USER'), config('PASS'))
streakinfo = lang.get_streak_info()
context = {
'username': username,
'streakinfo': streakinfo,
}
template_name = 'tests/test.html'
# if this is a POST request we need to process the form data
if request.method == 'POST':
# create a form instance and populate it with data from the saved answer dictionary:
print('POSTING TEST RESULTS')
form = TestForm(data=request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
print('PASSED')
# redirect to a new URL:
return redirect('main:success')
else:
print('FAILED')
if form.has_error:
print('FORM ERROR')
pass
# if a GET (or any other method) we'll create a blank form
else:
print('GETTING NEW TEST')
phrases = lang.get_known_phrases()
testwords = get_test_words(phrases)
wordsdict = get_word_dict(testwords)
form = TestForm(wordsdict)
context['form'] = form
return render(request, template_name, context)
Straight forward again...
models.py
class TestAnswers(models.Model):
phraseid = models.IntegerField(unique=True, blank=True, null=True)
question = models.TextField(blank=True, null=True)
answer = models.CharField(max_length=50, blank=True, null=True)
Here is where the magic happens. I'm super'ing the __init__ function of the inherited Form class. When the class is instantiated, it will evaluate the test_dict argument, which may or may not have been passed in by the view. If there is no test_dict, it must be a request for a new test, so I clear out the test model and create a new one with the randomly selected questions\answers passed in by the view. If there is no test_dict passed in, then it must be a post request, meaning I need to validate all the answers. Refer to the clean method for the form validation.
forms.py
class TestForm(forms.Form):
"""
Student test form
"""
def __init__(self, test_dict=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self._resource_path = os.path.join(settings.BASE_DIR, 'static/json')
self._json_path = os.path.join(self._resource_path, 'answers.json')
self._model = TestAnswers
i = 0
phraseid, answer, question = [], [], []
if test_dict is not None:
# A form get request should resolve new form data and
# store it in the database for comparison later on
# clear out the answers table
self._model.objects.all().delete()
# create a list of model objects to bulk insert
records = []
for item in test_dict:
record = self._model(
phraseid=test_dict[item]['id'],
answer=item,
question=test_dict[item]['definition']
)
phraseid.append(test_dict[item]['id'])
question.append(test_dict[item]['definition'])
answer.append(item)
records.append(record)
if records:
# Insert the records into the TestAnswers table
self._model.objects.bulk_create(records)
self.test_dict = test_dict
else:
# A form post request should check the form data against
# what was established during the get request
# Get all the objects in the test table
records = self._model.objects.all()
# Put all the object items into their respective lists
for r in records:
phraseid.append(r.phraseid)
answer.append(r.answer)
question.append(r.question)
for i in range(len(question)):
# Set the form fields
field_name = 'testword' + str(phraseid[i])
# Print the answers for debugging
print('id: ' + str(phraseid[i]))
print('question: ' + question[i])
print('answer:' + answer[i])
self.fields[field_name] = forms.CharField(label=question[i], max_length=100)
self.question = question
self.phraseid = phraseid
self.answer = answer
def clean(self):
# print('CLEANING DATA')
phraseid, answer, question = [], [], []
context = {}
i = 0
records = self._model.objects.all()
for r in records:
phraseid.append(r.phraseid)
answer.append(r.answer)
question.append(r.question)
# Get and check the results
for i in range(len(self.cleaned_data)):
field_name = 'testword' + str(phraseid[i])
result = self.cleaned_data.get(field_name)
if result != answer[i]:
self.add_error(field_name, 'Incorrect')
context[i] = question[i]
i += 1
return context
来源:https://stackoverflow.com/questions/54684678/manually-rendering-django-form-with-validation