I am learning Django forms and am trying to save form data. I have a working form, but I can't figure out to 'do' anything with the data entered on the form. Specifically, I am trying to do the following two things:
First, once the user submits the form, load a new page that states: "You searched for 'X'".
Second, have the form data interact with an existing database. Specifically, I have a model called 'Hashtag' that has two attributes: 'search_text' and 'locations'. I think the process would work as follows:
Send X to the Model ('Hashtag'),
If X is equal to an existing hashtag.search_text object in the database, then return a page with: "The following are the locations for 'X': 'Y'
If X doesn't equal an existing hashtag.search_text object in the database, then return a page with: "The following are the locations for 'X': no locations found".
Where,
X = user-inputted form data
Y = hashtag.locations.all() in a list
Thus far, I have the below:
models.py
from django.db import models
class Hashtag(models.Model):
"""
Model representing a specific hashtag search. The model contains two attributes:
1) a search_text (eg 'trump') for which there will be only one for database entry (the row),
2) a list of locations (eg ['LA, CA', 'LA, CA', 'NY, NYC', 'London, UK', 'London, United Kingdom']) for which there may be 0+ per search_text.
"""
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.TextField()
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# ISSUE: insert correct code, something like: return '[, ]'.join(hastagsearch.location_list for location in self.location.all())
pass
forms.py
from django import forms
from django.forms import ModelForm
from .models import Hashtag
class SearchHashtagForm(ModelForm):
""" ModelForm for user to search by hashtag """
def clean_hashtag(self):
data = self.cleaned_data['search_text']
# Check search_query doesn't include '#'. If so, remove it.
if data[0] == '#':
data = data[1:]
# return the cleaned data
return data
class Meta:
model = Hashtag
fields = ['search_text',]
labels = {'search_text':('Hashtag Search'), }
help_texts = { 'search_text': ('Enter a hastag to search.'), }
views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from .models import Hashtag
from .forms import SearchHashtagForm
def hashtag_search_index(request):
""" View for index page for user to input search query """
hashtag_search = get_object_or_404(Hashtag)
# If POST, process Form data
if request.method == 'POST':
# Create a form instance and populate it with data from request (binding):
form = SearchHashtagForm(request.POST)
# Check if form is valid
if form.is_valid():
# process the form data in form.cleaned_data as required
hashtag_search.search_text = form.cleaned_data['search_text']
# the reason we can use .save() is because we associated the form with the model as a ModelForm
hashtag_search.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('mapping_twitter:hashtag_search_query'))
# If GET (or any other method), create the default form
else:
form = SearchHashtagForm()
context = {'hashtag_search':hashtag_search, 'form':form}
return render(request, 'mapping_twitter/hashtag_search_query.html', context)
I am considering that a potential way to achieve this is to create another model and save the user-inputted form data there. I am wondering whether that is correct, and how that solution could be used to achieve the Second stated goal above :)
Thanks and apologies in advance if my explanation is a mess/plain wrong :/
EDIT
The EDIT below has made the following changes:
Updated models.py as per #Wiggy A.'s answer,
Updated views.py to include def results()
Included a link to the repo on GitHub.
models.py
from django.db import models
class Location(models.Model):
""" Model representing a Location, attached to Hashtag objects through a
M2M relationship """
name = models.CharField(max_length=140)
def __str__(self):
return self.name
class Hashtag(models.Model):
""" Model representing a specific Hashtag serch, containing two attributes:
1) A `search_text` (fe 'trump'), for which there will be only one per
database entry,
2) A list of `locations` (fe ['LA, CA', 'NY, NYC']), for which there
may be any number of per `search_text` """
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.ManyToManyField(Location, blank=True)
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# Return a list of location names attached to the Hashtag model
return self.locations.values_list('name', flat=True).all()
views.py
...
def results(request):
""" View for search results for `locations` associated with user-inputted `search_text` """
search_text = hashtag_search
location_list = Hashtag.display_locations()
context = {'search_text':search_text, 'location_list':location_list}
return render(request, 'mapping_twitter/results.html')
The full repo can be found here: https://github.com/darcyprice/Mapping-Data
EDIT 2
The EDIT below makes the following changes:
Updated views.py to include #Wiggy A.'s suggested amendment of def results()
Included a copy of the ERROR message received due to the updated changes.
Although I copied directly from the Mozilla tutorial (https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Forms), I suspect that the line: hashtag_search.search_text = form.cleaned_data['search_text'] doesn't correctly store hashtag_search.
ERROR
NameError at /search_query/
name 'hashtag_search' is not defined
Request Method: POST
Request URL: http://ozxlitwi.apps.lair.io/search_query/
Django Version: 2.0
Exception Type: NameError
Exception Value:
name 'hashtag_search' is not defined
Exception Location: /mnt/project/mapping_twitter/views.py in hashtag_search_index, line 24
Python Executable: /mnt/data/.python-3.6/bin/python
Python Version: 3.6.5
Python Path:
['/mnt/project',
'/mnt/data/.python-3.6/lib/python36.zip',
'/mnt/data/.python-3.6/lib/python3.6',
'/mnt/data/.python-3.6/lib/python3.6/lib-dynload',
'/usr/local/lib/python3.6',
'/mnt/data/.python-3.6/lib/python3.6/site-packages']
views.py
def hashtag_search_index(request):
""" View for index page for user to input search query """
# If POST, process Form data
if request.method == 'POST':
# Create a form instance and populate it with data from request (binding):
form = SearchHashtagForm(request.POST)
# Check if form is valid
if form.is_valid():
hashtag_search.search_text = form.cleaned_data['search_text']
hashtag_search.save()
# redirect to a new URL
return HttpResponseRedirect(reverse('mapping_twitter:results'))
# If GET (or any other method), create the default form
else:
form = SearchHashtagForm()
context = {'hashtag_search':hashtag_search, 'form':form}
return render(request, 'mapping_twitter/hashtag_search_index.html', context)
def results(request):
""" View for search results for `locations` associated with user-inputted `search_text` """
search_text = hashtag_search
location = get_object_or_404(Hashtag, search_text=search_text)
location_list = location.display_locations()
context = {'search_text':search_text, 'location_list':location_list}
return render(request, 'mapping_twitter/results.html', context)
Turn the locations attribute into a M2M field. That sounds like what you need here. Keep in mind that this is untested code.
models.py
from django.db import models
class Location(models.Model):
""" A model representing a Location, attached to Hashtag objects through a Many2Many relationship """
name = models.CharField(max_length=140)
def __str__(self):
return self.name
class Hashtag(models.Model):
"""
Model representing a specific hashtag search. The model contains two attributes:
1) a search_text (eg 'trump') for which there will be only one for database entry (the row),
2) a list of locations (eg ['LA, CA', 'LA, CA', 'NY, NYC', 'London, UK', 'London, United Kingdom']) for which there may be 0+ per search_text.
"""
search_text = models.CharField(max_length=140, primary_key=True)
locations = models.ManyToManyField(Location)
def __str__(self):
""" String for representing the Model object (search_text) """
return self.search_text
def display_locations(self):
""" Creates a list of the locations """
# This will return a list of location names attached to the Hashtag model
return self.locations.values_list('name', flat=True).all()
views.py
...
def results(request):
""" View for search results for `locations` associated with user-inputted `search_text` """
search_text = hashtag_search
location = get_object_or_404(Hashtag, search_text=search_text)
location_list = location.display_locations()
context = {'search_text':search_text, 'location_list':location_list}
return render(request, 'mapping_twitter/results.html')
Related
I'm creating a quiz project using Django with MongoEngine. Multiple Choice Questions and quiz are two separated apps. I want to fetch multiple choice in quiz based on m_id (unique number for each multiple choice). I'm a beginner and need a little help. How can i achieve thisMCQS Model
from django_mongoengine import Document, EmbeddedDocument, fields
class multichoice(Document):
m_id = fields.IntField(min_value=1, verbose_name='MCQ Id')
m_title = fields.StringField(primary_key=True, max_length=255, verbose_name='Title')
m_question = fields.StringField(verbose_name='Question')
m_alternatives = fields.ListField(fields.StringField(),verbose_name='Alternatives')
def __str__(self):
return self.m_title
Quiz Model
from django_mongoengine import Document, EmbeddedDocument, fields
from mcqs.models import *
class quiz(Document):
q_id = fields.IntField(min_value=1, verbose_name='Quiz ID')
q_title = fields.StringField(primary_key=True, max_length=255, verbose_name='Title')
q_s_description = fields.StringField(max_length=255, verbose_name='Description')
q_questions = fields.ListField(fields.IntField(), verbose_name='Question Numbers', blank=True)
def __str__(self):
return self.q_title
MCQ Views
def view_multichoice(request, m_id):
get_md = multichoice.objects(m_question_number = m_id)
return render(request, 'mcqs/mcq.html', {'get_md':get_md})
Quiz Views
def view_quiz(request, q_id):
get_q = quiz.objects(q_id = q_id)
return render(request, 'quiz/quiz.html', {'get_q':get_q})
Current Result
Expected Result
EDIT 1
Quiz Views
from django.shortcuts import render
from django.http import HttpResponse
from .models import *
from mcqs.models import *
def view_quiz(request, q_id):
quiz_object = quiz.objects(q_id=q_id)[0]
multichoice_objects = [multichoice.objects(m_id=id) for id in quiz_object.q_questions]
get_q = [objects[0].m_question for objects in multichoice_objects if objects]
return render(request, 'quiz/quiz.html', {'get_q':get_q})
Quiz Template
{{ get_q }}
You are returning the question documents as it is. What you should be doing is, fetch the multichoice documents corresponding to the question ids, and then get the question field from each of those documents.
Change the second line in quiz views to this:
# Get the question document corresponding to given id
# objects method returns a list. Get the first one out of it.
# Here, I've assumed there's exactly 1 quiz document per ID. Handle the edge cases
quiz_object = quiz.objects(q_id = q_id)[0]
# Get the multiple choice documents corresponding to the ids in q_questions list
# Remember that objects method returns a list. Hence this is a list of lists
multichoice_objects = [multichoice.objects(m_id=id) for id in quiz_object.q_questions]
# Get the actual questions from the multichoice documents
get_q = [objects[0].m_question for objects in multichoice_objects if objects]
Ideally, you should be making multichoice as an EmbeddedDocument and the q_questions field in quiz model as EmbeddedDocumentListField. But the drawback here will be, you can't query EmbeddedDocuments independently. So you wont be able to do multichoice.objects(m_question_number=m_id).
You can read more about EmbeddedDocuments here
I am trying to make an publishing option, so i use this
class Article(models.Model):
publish_options = models.CharField(max_length=50)
Now in my form, i used forms.CheckboxSelectMultiple widget. so i have this
PUBLISH_VISIBILITY = (
('All', 'All'),
('Paid-users', 'Paid-users'),
('Free Users', 'Free Users'),
('Public', 'Public'),
)
class PortalNoteForm(ModelForm):
publish_options = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=PUBLISH_VISIBILITY)
class Meta:
model = Article
Now in my view i get the values for the checkbox like this
if request.method == 'POST':
form = PortalNoteForm(request.POST)
if form.is_valid():
school_article = form.save(commit=False)
school_article.publish_options = form.cleaned_data['publish_options']
school_article.school_creator = admin
school_article.save()
return HttpResponseRedirect(reverse('going to somewhere'))
else:
form = PortalNoteForm()
context = {'form':form, 'notes':notes}
Okay this works fine saving the value of the publish_option but as a list, even if you select only one checkbox its value comes as a list. Now the problem here is i cant get to stop these publish_options value from displaying as a list in django template. I have tried iterating over them but no way. I really need help.
Well you already had a form, why do you still getting data from request.POST? Form is suppose to take request.POST and convert data into a more convenient way for you to use:
views.py
def view_func(request):
form = PortalNoteForm(request.POST or None)
if form.is_valid():
options = form.cleaned_data['public_options']
# now you have options so use it
I am trying to write a form that allows the user to select as many users from a specific group as they want. However when I try to use the list of users as an option I get an error saying that 'User' object does not support indexing.
Its a fairly standard form, the main difference is that the group is filtered based on a kwarg passed to the form. The form is passed a project_id (project object primary key) and it then finds the group associated with that project and generates the field.
From forms.py
class ModifyTeamForm(forms.Form):
action = ChoiceField(choices=[('remove', 'Remove users'), ('promote', 'Promote to lead.')])
def __init__(self, *args, **kwargs):
# The project to get the team for
project_id = kwargs.pop('project_id', None)
super(ModifyTeamForm, self).__init__(*args, **kwargs)
project = Project.objects.get(pk=project_id)
# Team for this project
team = User.objects.filter(groups__name=project.project_name)
# Create a form field to select current team members
current_team = MultipleChoiceField(required=True, choices = team, widget=CheckboxSelectMultiple)
# Add the field
self.fields['current_team'] = current_team
My views.py
#login_required
def team(request, project_id):
if request.method == "POST":
# Not yet implemented
return
else:
form = ModifyTeamForm(project_id=project_id)
template = loader.get_template('projects/team.html')
context = RequestContext(request, {
'form': form,
})
return HttpResponse(template.render(context))
It's because MultipleChoiceField.choices is expected to be a 2d Array effectively (https://docs.djangoproject.com/en/1.7/ref/forms/fields/#django.forms.ChoiceField.choices).
So you could do something like this:
team = [(u.pk, u.email) for u in User.objects.filter(groups__name=project.project_name)]
And that will return you a list continaing the combintation of
[('user1.pk', 'user1.email'), ('user2.pk', 'user2.email'),...]
which will be useable as the choices.
using CreateView class, I want to save multiple data entries.
example inputs:
item is "apple,banana,carrots"
location is "location 1"
I want to save them to database like this:
[apple, location 1]
[banana, location 1]
[carrots, location 1]
#model.py
class Inventory(models.Model):
item = models.CharField(max_length=14)
location = models.CharField(max_length=10)
#forms.py
class InventoryCreateForm(forms.ModelForm):
item = forms.CharField(widget=forms.Textarea(attrs={'rows': 8,
'cols': 14}))
class Meta:
model = Inventory
#views.py
class InventoryCreateView(CreateView):
model = Inventory
form_class = InventoryCreateForm
Thank you
You need to override the "form_valid()" method used by the createview.
You then need to read in the form data
def form_valid(self,form):
self.object = form.save(commit=False)
foo = self.object.bar #your data is in the object
Then because you are using a textfield you need to somehow split the data passed into the form and loop over those values. Ideally you will want a list of items ['apple', 'banana', 'pear']
then take the location out of the list and store that into a variable that can be used later on location_variable.
Once you have the data in the form you want you then need to instantiate the Inventory model
from foo.models import Inventory #import at the top of your file
for item is list:
inventory = Inventory()
inventory.item = item
inventory.location = location_variable
inventory.save()
I hope this answer can help you in some way, if you would like further details on Class based view, visit ccbv where all of the information for each view is listed.
Otherwise you can look in the django Form docs for a more suitable form to use.
hopefully this helps anyone else who comes to this page.
Here is what I did:
class TrainingBulkCreateForm(ModelForm):
# todo: could we make the original user object a multiple choice field instead?
extra_user = forms.ModelMultipleChoiceField(queryset=User.objects.filter(is_active=True), required=True)
class Meta(object):
model = Training
fields = '__all__'
def post(self, request, *args, **kwargs):
result = super().post(request, *args, **kwargs)
users = request.POST.getlist('extra_user')
if users:
# modify request.POST data then re-save the additional users
for user in users:
# Need to copy the data because the initial QueryDict is immutable data
postdata_copy = request.POST.copy()
postdata_copy['user'] = user
form2 = TrainingBulkCreateForm(postdata_copy)
form2.save()
return result
I have my item field (mine is user) split into two form fields - the original item which is 1 object like apple.
Then I also have extra_user as a second form field. This takes multiple objects like [banana, carrots]
In the view I invoke the super().post(...) to save the initial apple object.
Then I check for the extra_user field to see if there are multiple foods. If there are extras, I copy the immutable QueryDict object request.POST as postdata_copy.
Then I modify postdata_copy so instead of having apple we replace with banana. (This basically just duplicates the apple form into a new copy with banana and re-saves the form). Then we loop again and replace apple with carrots.
Note that my Form object uses ModelMultipleChoiceField for extra_user object. This has better data integrity than typing in raw text.
I have models for Application and Role. Role is linked to a FK Role_type, which is linked by FK to Applications that can use those Role_types (this is a bit of an over-simplication for the question, but I think it suffices). I need a way to create a form to make a new Application, and also to create records assigning associated roles to people (although they can be left blank.)
I have gotten as far as creating the form for the Application and having the associated Role-Types appear on the page, with dropdowns to be populated with a user. Hitting submit, though, didn't create any of the associated Role records. All of my research seems to keep coming back to Inline Model Forms, but the docs aren't really making sense to me--the inputs in the example don't seem to correlate to what I need.
I know this may seem like a duplicate, but trust me when I say I've looked at every SO question that seems to relate to this!
EDIT: My POST looks like this: QueryDict: {u'roles-MAX_NUM_FORMS': [u'1000'], u'roles-1-role_type': [u'4'], u'roles-0-user': [u'1'], u'app-owner': [u'1'], u'app-name': [u'1234'], u'app-serviceTier': [u''], u'app-jiraProject': [u''], u'roles-TOTAL_FORMS': [u'2'], u'roles-1-user': [u''], u'roles-0-role_type': [u'3'], u'csrfmiddlewaretoken': [u'eGsDwtsSQJfl0'], u'roles-INITIAL_FORMS': [u'2']}>. Printing RolesFormSet gives me the exact same output (see comment below)
models.py
class Item(models.model):
name = models.CharField(max_length=255)
roles = models.ManyToManyField(User, through='Role')
class Application(Item):
other_assorted_attributes = foo
class RoleType(models.Model):
name = models.CharField(max_length=255)
class ItemTypeRoleMapping(models.Model):
''' pairs role-types (e.g., Developer) with an Item class they are relevant to'''
roleType = models.ForeignKey(RoleType)
itemType = models.CharField(max_length=255, choices=itemChoices)
class Role(models.Model):
role_type = models.ForeignKey(RoleType)
user = models.ForeignKey(User)
item = models.ForeignKey(Item)
views.py
def buildRolesFormset(itemClass):
''' given an item, build a form for all associated roles '''
roleTypesForItem = ItemTypeRoleMapping.objects.all().filter(itemType=itemClass.__name__)
applicable_roles = [{'role_type': roleType} for roleType in roleTypesForItem]
# formset = rolesFormSet(initial=initial, prefix='roles')
RoleFormSet = inlineformset_factory(Application, Role, extra=len(roleTypesForItem), can_delete=False)
formset = RoleFormSet()
for subform, data in zip(formset.forms, applicable_roles):
subform.initial = data
return formset
def new(request):
''' Create a new application '''
user = request.user
# check permission
if request.method == 'POST':
appform = AppForm(request.POST, prefix='app')
if appform.is_valid():
app = appform.save(commit=False)
rolesInlineFormSet = inlineformset_factory(Application, Role)
# pdb.set_trace()
rolesFormSet = rolesInlineFormSet(request.POST, instance=app, prefix='roles')
if rolesFormSet.is_valid():
rolesFormSet.save()
else:
print rolesFormSet.errors
app = appform.save()
# check rolesFormSet
return redirect(reverse('index'))
else:
appform = AppForm(prefix='app')
rolesFormSet = buildRolesFormset(Application)
return render(request, 'who/editapp.html',
{'appform': appform,
'rolesFormSet': rolesFormSet
})
Tricky to tell without more information, but it looks like you're not saving your rolesFormset in the view. You need to call rolesFormset.save() alongside your form.save() call. Additionally, I suppose you want to attach the roles to the created app? Something like this in your view should work:
if request.method == 'POST':
form = AppForm(request.POST)
rolesFormset = RoleForm(request.POST)
if form.is_valid() and rolesFormset.is_valid():
app = form.save()
roles = rolesFormset.save()
for role in roles:
app.roles.add(role)
return redirect(reverse('index'))
Update: Presuming the models.py is out-of-date, and Role does in fact have a foreignKey to User, the problem will be that you're setting a prefix here:
rolesFormSet = rolesInlineFormSet(request.POST, instance=app, prefix='roles')
but not in your buildRolesFormset function. In that function, do:
formset = RoleFormSet(prefix='roles')