I am currently using models to have users enter data, using templates (not admin) that is then stored, at which point the users can then see all the data they entered. I would like to also give users the ability to delete specific entries, this would be done using object ids to identify and delete specific objects.
Here is my views.py:
#login_required(login_url='/login/')
def fav(request):
context = RequestContext(request)
#This returns all of the data the user has entered
favorites_list = StockTickerSymbol.objects.filter(user=request.user).order_by('-added_date')
`
#This is to try to get the ID of every object in favorites_list and append it to a list
for obj in favorites_list:
stock_id = []
stock_id.append(obj.id)
#Here is where the form is processed to save the data the user has entered
if request.method == 'POST':
form = FavoritesForm(request.POST)
if form.is_valid():
stock = form.save(commit=False)
stock.user = request.user
stock.save()
return redirect(fav)
else:
print form.errors
else:
form = FavoritesForm()
context_dict = {'favorites': favorites_list, 'form':form, 'stock_id':stock_id}
return render_to_response('favorites/favorites.html', context_dict, context)
def delete(request, id):
stock_to_delete = get_object_or_404(StockTickerSymbol, pk=id).delete()
return redirect(fav)
Here is my urls.py:
url(r'^favorites/$', views.fav, name='favorites'),
url(r'^add_favorites/$', views.add_fav, name='add favorites'),
url(r'^delete/(?P<id>\d+)/$', views.delete, name='delete')
And this is the part of my template file responsible for deleting
{% for id in stock_id %}
<div align="right">Delete</div>
{% endfor %}
My problem with this code, is that the delete link in my template only gives the first object ID for all the links. For example if there are three submissions for the user, and there id's are 1,2,3. The delete link will read "/delete/1" for all the submissions, thus only allowing users to delete their first submission. Any idea on how I can solve this?
Your problem is here:
for obj in favorites_list:
stock_id = []
stock_id.append(obj.id)
You are reinitializing inside the loop.
Try this
stock_id = []
for obj in favorites_list:
stock_id.append(obj.id)
Note that you can also do:
favorites_list = StockTickerSymbol.objects.filter(user=request.user).order_by('-added_date')
stock_ids = list(facorites_list.values_list('id', flat=True)) #IMO - It is a good idea to name a list with plural for readability
Also, in your delete method - See if the user does have permission to delete the object. If not, anyone can hit this url with some random id and start deleting the objects in the database.
I would start off by adding the login_required decorator, followed by adding a created_by or attaching a group associated with the model, which need to be verified before allowing the user to delete the object.
EDIT
{% for fav in favorite_list %}
<div class="fav">
{{fav.name}}
</div>
Delete me
{% endfor %}
Now you can do away with the id list.
Related
I am working on a Django project and I want to retrieve the category name in my template like Adventure, Hiking.. but instead, it's displaying ids of the category like 1,2,3. Instead of displaying the name of the category. Can someone help me out with this?
{% for data in tourData %}
{{data.category}}
{% endfor %}
models.py
class Tour(models.Model):
category_choices=[('1','Adventure'),('2','Trekking'),('3','Hiking')]
category=models.CharField(max_length=1,choices=category_choices,default='1')
view.py
def recommendations(request):
if request.method=='POST':
contents=Tour.objects.all()
category1= request.POST['category'] #Retrieves the category entered by the user
category2=request.POST['place']
tourData = Tour.objects.all().filter(category=category1,place=category2).order_by('-rating').values()
context={
'tourData':tourData
}
return render(request,"base/recommendations.html",context)
else:
tourData=Tour.objects.all().order_by('-rating').values()
context={'tourData':tourData
}
return render(request,"base/recommendations.html",context)
You need to use get_field_display in your template, i.e.
{{ data.get_category_display }}
This will show the second tuple value in your model choices rather than the first (the first is what actually goes into the database).
As an aside, I would recommend changing the format of your tourData variable from camelCase to snake_case - tour_data - it's more pythonic.
I'm using Django 2.1.
I'm having a problem with a CreateView because I need to redirect to the update url, but that url contains one argument that is created manually after verifying that the form is valid.
This is the view code:
class ProjectCreateInvestmentCampaignView(LoginRequiredMixin, SuccessMessageMixin, generic.CreateView):
template_name = 'webplatform/project_edit_investment_campaign.html'
model = InvestmentCampaign
form_class = CreateInvestmentCampaignForm
success_message = 'Investment campaign created!'
def get_success_url(self):
return reverse_lazy('project-update-investment-campaign',
args=(self.kwargs['pk'], self.object.campaign.pk, self.object.pk))
def form_valid(self, form):
project = Project.objects.get(pk=self.kwargs['pk'])
form.instance.investment_type = "A"
form.instance.contract_type = "CI"
form.instance.history_change_reason = 'Investment campaign created'
valid = super(ProjectCreateInvestmentCampaignView, self).form_valid(form)
if valid:
campaign = CampaignBase.objects.create(project=project, )
form.instance.campaign = campaign
form.instance.campaign.project = project
form.instance.campaign.creation_date = timezone.now()
form.save()
return valid
As you can see, on the form_valid I validate first the form, and then I create the object campaign and assign all the related data. This is working fine.
The problem came when I changed the get_success_url to fit my use case, that is redirecting to the update view.
I debugged and saw that at the moment I create the variable valid on the form_valid, it checks the success url, and that triggers me the following error:
Exception Type: AttributeError
Exception Value:
'NoneType' object has no attribute 'pk'
Exception Location: /Volumes/Archivos/work/i4b/webplatform/views/investor_campaign_views.py in get_success_url, line 25
I asume that the error is because the campaign is not created yet so it's trying to get the pk from a non existing object.
The thing is that I cannot create the campaign if the form is not validated, but I need the campaign to make the url working (that url is working as it is on the UpdateView that I already have).
It will only invoke get_success_url after form_valid. So it's up to form_valid to create and save the objects needed. If it's valid for them not to be created, you need a different approach. Maybe initialize (say) self.campaign_pk = 0, update it if a campaign can be created with the pk of the campaign object, and let the next view sort out what to do when pk==0. Or,
...
args=(self.kwargs['pk'],
self.object.campaign.pk if self.object.campaign else 0,
self.object.pk))
(I don't fully follow your code so I might be barking up the wrong tree here)
It may be that you don't want CreateView but FormView, which doesn't handle object creation for you, so you may find greater flexibility over how to process a valid form that nevertheless cannot be fully honoured all the time. Or even, just a plain old function-based view, in which you can process two or more forms and be far more able to decide on conditions that constitute non-validity even after all the forms have technically validated.
This is a function-based view structure I have used where I have two forms to process, and a fairly long but boring set of operations to do after BOTH forms validate:
def receive_view( request):
# let's put form instantiation in one place not two, and reverse the usual test. This
# makes for a much nicer layout with actions not sandwiched by "boilerplate"
# note any([ ]) forces invocation of both .is_valid() methods
# so errors in second form get shown even in presence of errors in first
args = [request.POST, ] if request.method == "POST" else []
batchform = CreateUncWaferBatchForm( *args, layout=CreateUncWaferBatchLayout )
po_form = CreateUncWaferPOForm( *args, layout = CreateUncWaferPOLayout, prefix='po')
if request.method != "POST" or any(
[ not batchform.is_valid(), not po_form.is_valid() ]):
return render(request, 'wafers/receive_uncoated.html', # can get this out of the way at the top
{'batchform': batchform,
'po_form': po_form,
})
#it's a POST, everything is valid, do the work
...
return redirect('appname:viewname', ...)
For me, get_success_url was not invoked as the form was not valid (was invalid) and I didn't know. You can override form_invalid(self, form) to control the behavior.
Also, consider this block of code to show any errors in your template
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field, errors in form.errors.items %}
{% for error in errors %}
<b>{{ field }}</b>: {{ error }}
{% endfor %}
{% endfor %}
</div>
{% endif %}
I'm trying to access the information in my gadb_action model based on the action_ids in my gadb_vote model. I'm initially only getting the information for a particular legislator and then attempting to get the bills associated with the actions that legislator has voted on.
Right now, my action_list is only storing action_ids, but not the related information from the gadb_action model that I want to use in my template.
What is the best way to store that information outside of the for loop to be accessed by the template? Is there a way to write to an empty QuerySet?
Thanks in advance for any and all help!
view
def each_member(request,legislator_id):
each_member = get_object_or_404(gadb_legislator, legislator_id=legislator_id)
each_vote = gadb_vote.objects.filter(legislator_id=legislator_id)
action_list = []
for i in each_vote:
action = gadb_action.objects.filter(action_id=i.action_id)
action_list.append(action)
context = {
'each_member': each_member,
'each_vote': each_vote,
'action_list': action_list
}
return render(request, "eachmember.html", context)
models
class gadb_action(models.Model):
action_id = models.IntegerField(unique=False, max_length=4, primary_key=True)
bill_id = models.IntegerField(unique=False, max_length=12)
class gadb_vote(models.Model):
vote_id = models.IntegerField(unique=False, max_length=11,primary_key=True)
legislator_id = models.IntegerField(unique=False, max_length=11)
action_id = models.IntegerField(unique=False, max_length=11)
template
{% for i in action_list %}
{{i.bill_id}}
{{i.action_id}}
{% endfor %}
Your models are broken.
For a start, although it's not directly related to the question, you need to define your primary keys as AutoFields so that they are autoincremented every time a new entity is added. Otherwise you'll get all sorts of errors when you save a new row. (Even better, don't define the PK at all, and let Django add it automatically.)
Secondly, as lalo says, you should have ForeignKeys from Action to Bill, and from Vote to Action and Vote to Legislator. That way you can get the relevant information with a single query, and follow the foreign keys as required in your template.
(Also, Django already includes the app name in the underlying table name: no need to prefix everything with 'gadb'.)
class Action(models.Model):
bill = models.ForeignKey(Bill)
class Vote(models.Model):
legislator = models.ForeignKey(Legislator)
action = models.ForeignKey(Action)
View:
def each_member(request,legislator_id):
actions = Action.objects.filter(vote__legislator_id=legislator_id)
return render(request, "eachmember.html", {'action_list': actions})
Template:
{% for action in actions %}
{{ action.bill.name }}
{{ action.someotherfield }}
{% endfor %}
I'm using Django Forms to do a filtered/faceted search via POST, and I would like to Django's paginator class to organize the results. How do I preserve the original request when passing the client between the various pages? In other words, it seems that I lose the POST data as soon as I pass the GET request for another page back to my views. I've seen some recommendations to use AJAX to refresh only the results block of the page, but I'm wondering if there is a Django-native mechanism for doing this.
Thanks.
If you want to access the store data in later request, you would have to store it somewhere. Django provides several ways to archive this:
1) You can use sessions to store the query: Every visitor who visits your site will get an empty session object and you can store whatever you want inside this object, which acts like a dict. Drawback: A single visitor can't do multiple searches with pagination concurrently.
2) Use cookies: If you set a cookie which is stored on the client side, the browser will append the data of the cookie to each request where you can access it. Cookies are more server friendly, because you don't need a session manager for them on the server, but the data stored in cookies is visible (and editable) to the client. Drawback: same as before.
3) Use hidden fields: You can add a form with some hidden fields on your search-result page and store the query inside them. Then, the client will resend the query whenever you submit the form. Drawback: You must use a form with submit buttons for the pagination on your page (simple links wont work).
4) Create Links which contain the query: Instead of using POST, you can also use GET. For example, you could have a link like "/search/hello+world/?order=votes" and "paginated links" like "/search/hello+world/2/?order-votes". Then the query can be easily retrieved from the URL. Drawback: The maximum amount of data you can send via GET is limited (But that shouldn't be a problem for a simple search).
5) Use a combination: You might want to store all the data in a session or a database and access them via a generated key which you can put in the URL. URLs might then look like "/search/029af239ccd23/2" (for the 2nd page) and you can use the key to access a huge amount of data which you have stored before. This eliminates the drawback of solution 1 as well as that of solution 4. New drawback: much work :)
6) Use AJAX: With ajax you can store the data inside some js-variables on the client side, which can then passed to the other requests. And since ajax will only update your result list, the variables aren't getting lost.
Reading the very nice answer from tux21b I decided to implement the first option, i.e., to use the session to store the query. This is an application that searches real estate databases. Here is the view code (using django 1.5):
def main_search(request):
search_form = UserSearchForm()
return render(request, 'search/busca_inicial.html', {'search_form': search_form})
def result(request):
if request.method == 'POST':
search_form = UserSearchForm(request.POST)
if search_form.is_valid():
# Loads the values entered by the user on the form. The first and the second
# are MultiChoiceFields. The third and fourth are Integer fields
location_query_list = search_form.cleaned_data['location']
realty_type_query_list = search_form.cleaned_data['realty_type']
price_min = search_form.cleaned_data['price_min']
price_max = search_form.cleaned_data['price_max']
# Those ifs here populate the fields with convenient values if the user
# left them blank. Basically the idea is to populate them with values
# that correspond to the broadest search possible.
if location_query_list == []:
location_query_list = [l for l in range(483)]
if realty_type_query_list == []:
realty_type_query_list = [r for r in range(20)]
if price_min == None:
price_min = 0
if price_max == None:
price_max = 100000000
# Saving the search parameters on the session
request.session['location_query_list'] = location_query_list
request.session['price_min'] = price_min
request.session['price_max'] = price_max
request.session['realty_type_query_lyst'] = realty_type_query_list
# making a query outside the if method == POST. This logic makes the pagination possible.
# If the user has made a new search, the session values would be updated. If not,
# the session values will be from the former search. Of course, that is what we want because
# we want the 'next' and 'previous' pages correspond to the original search
realty_list_result = FctRealtyOffer.objects.filter(location__in=request.session['location_query_list']
).filter(price__range=(request.session['price_min'], request.session['price_max'])
).filter(realty_type__in=request.session['realty_type_query_lyst'])
# Here we pass the list to a table created using django-tables2 that handles sorting
# and pagination for us
table = FctRealtyOfferTable(realty_list_result)
# django-tables2 pagination configuration
RequestConfig(request, paginate={'per_page': 10}).configure(table)
return render(request, 'search/search_result.html', {'realty_list_size': len(realty_list_result),
'table': table})
Hope it helps!If anyone has any improvement to suggest, be welcome.
As #rvnovaes, a way to use session to solve the matter.
The drawback of his solution is that if there are many search fields you have to write many lines of code, and also if you show the search form in the result page, all the fields will be blank, while they should keep their values.
So I'd rather save all the post data in session, and at the beginning of the view force the value of request.POST and request.method if a session is defined:
""" ... """
if not request.method == 'POST':
if 'search-persons-post' in request.session:
request.POST = request.session['search-persons-post']
request.method = 'POST'
if request.method == 'POST':
form = PersonForm(request.POST)
request.session['search-persons-post'] = request.POST
if form.is_valid():
id = form.cleaned_data['id']
""" ... """
More info here
I did this in my web application with get parameters Maybe i can help you :
Views.py
class HomeView(ListView):
model = Hotel
template_name = 'index.html'
paginate_by = 10 # if pagination is desired
def get_queryset(self):
qs = super().get_queryset()
kwargs = {}
if 'title' in self.request.GET:
title = self.request.GET.get('title')
if title != '':
kwargs['title__icontains'] = title
if 'category' in self.request.GET:
category = self.request.GET.get('category')
if category:
kwargs['category_id'] = category
if 'size' in self.request.GET:
size = self.request.GET.get('size')
if size:
kwargs['size_id'] = size
if 'service' in self.request.GET:
service = self.request.GET.get('service')
if service:
kwargs['service_id'] = service
if 'ownership' in self.request.GET:
ownership = self.request.GET.get('ownership')
if ownership:
kwargs['ownership_id'] = ownership
qs = qs.filter(**kwargs)
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form_init = {}
form = SearchForm()
if self.request.GET.items():
try:
parameters = self.request.GET.items()
except KeyError:
parameters = {}
for key, value in parameters:
for field in form.fields:
if key == field:
form_init[key] = value
form.initial = form_init
if 'title' in self.request.GET:
title = self.request.GET.get('title')
if title != '':
context.update({
'title': title
})
if 'category' in self.request.GET:
category = self.request.GET.get('category')
context.update({
'category': category
})
if 'size' in self.request.GET:
size = self.request.GET.get('size')
context.update({
'size': size
})
if 'service' in self.request.GET:
service = self.request.GET.get('service')
context.update({
'service': service
})
if 'ownership' in self.request.GET:
ownership = self.request.GET.get('ownership')
context.update({
'ownership': ownership
})
context.update({
'search_form': form
})
return context
Pagination file html
<div class="row">
{% if is_paginated %}
<nav aria-label="...">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?category={{category}}&size={{size}}&service={{service}}&ownership={{ownership}}&page={{ page_obj.previous_page_number }}">Previous</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">Previous</span></li>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?category={{category}}&size={{size}}&service={{service}}&ownership={{ownership}}&page={{ page_obj.next_page_number }}">Next</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
You can ask request object if it's ajax, simply request.is_ajax. This way you can detect, whether it's first post request or further questions about the next pages.
Have the search form and the results display on one single django template. Initially, use css to hide the results display area. On POSTing the form, you could check to see if the search returned any results and hide the search form with css if results exist. If results do not exist, use css to hide the results display area like before. In your pagination links, use javascript to submit the form, this could be as simple as document.forms[0].submit(); return false;
You will need to handle how to pass the page number to django's paging engine.
My suggestion would be to store the post request using a session or a cookie. In case the post data is sensitive, you should use session to store it. The code below contains my logic to implement it using session.
def index(request):
is_cookie_set = 0
# Check if the session has already been created. If created, get their values and store it.
if 'age' in request.session and 'sex' in request.session:
age = request.session['age']
sex = request.session['sex']
is_cookie_set = 1
else:
# Store the data in the session object which can be used later
request.session['age'] = age
request.session['sex'] = sex
if(request.method == 'POST'):
if(is_cookie_set == 0): # form submission by the user
form = EmployeeForm(request.POST)
sex = form.cleaned_data['sex']
age = form.cleaned_data['age']
if form.is_valid():
result = Employee.objects.all(sex=sex,age_gte=age) # filter all employees based on sex and age
else: # When the session has been created
result = Employee.objects.all(sex=sex,age_gte=age)
paginator = Paginator(result, 20) # Show 20 results per page
page = request.GET.get('page')
r = paginator.get_page(page)
response = render(request, 'app/result.html',{'result':result})
return response
else:
form = EmployeeForm()
return render(request,'app/home.html',{'form':form})
You should also check if the post fields are empty or not and change the logic according to it. You can also store the whole post request in the session as suggested by #abidibo.
You can also use cookies for the same. I have explained it here
The Below code is working, the first request is a GET request, which accesses the form, will go directly to the else block.
Once the user puts up a search query, results will be shown, which will be a post request, and 2nd if block will be activated, this request we will store in a session.
When the user accesses 2nd search page, it will be a GET request, but we are checking whether there is an active pagination session, and also checking whether it's not a page request of GET. At this point 1st if block will be trigerred.
def search(request):
if not request.method == "POST" and 'page' in request.GET:
if 'search-query' in request.session:
request.POST = request.session['search-query']
request.method = 'POST'
if request.method == 'POST':
form = Search_form(request.POST)
request.session['search-query'] = request.POST
if form.is_valid():
search_query = form.cleaned_data.get('search_query')
search_parameter = form.cleaned_data.get('search_parameter')
print(search_query, search_parameter)
queryset_list = CompanyRecords.objects.filter(**{f'{search_parameter}__icontains': search_query}).exclude(
company_name__isnull=True).exclude(description__isnull=True).exclude(phones__isnull=True).exclude(
emails__isnull=True)[:5]
page = request.GET.get('page', 1)
paginator = Paginator(queryset_list, 2)
try:
queryset = paginator.page(page)
except PageNotAnInteger:
queryset = paginator.page(1)
except EmptyPage:
queryset = paginator.page(paginator.num_pages)
return render(request, 'search/search_results.html', {'queryset': queryset})
else:
context = {
'form': Search_form()
}
return render(request, 'search/search.html', context)
I'm working on my first Django application. In short, what it needs to do is to display a list of film titles, and allow users to give a rating (out of 10) to each film. I've been able to use the {{ form }} and {{ formset }} syntax in a template to produce a form which lets you rate one film at a time, which corresponds to one row in a MySQL table, but how do I produce a form that iterates over all the movie titles in the database and produces a form that lets you rate lots of them at once?
At first, I thought this was what formsets were for, but I can't see any way to automatically iterate over the contents of a database table to produce items to go in the form, if you see what I mean.
Currently, my views.py has this code:
def survey(request):
ScoreFormSet = formset_factory(ScoreForm)
if request.method == 'POST':
formset = ScoreFormSet(request.POST, request.FILES)
if formset.is_valid():
return HttpResponseRedirect('/')
else:
formset = ScoreFormSet()
return render_to_response('cf/survey.html', {
'formset':formset,
})
And my survey.html has this:
<form action="/survey/" method="POST">
<table>
{{ formset }}
</table>
<input type = "submit" value = "Submit">
</form>
Oh, and the definition of ScoreForm and Score from models.py are:
class Score(models.Model):
movie = models.ForeignKey(Movie)
score = models.IntegerField()
user = models.ForeignKey(User)
class ScoreForm(ModelForm):
class Meta:
model = Score
So, in case the above is not clear, what I'm aiming to produce is a form which has one row per movie, and each row shows a title, and has a box to allow the user to enter their score.
If anyone can point me at the right sort of approach to this, I'd be most grateful.
"At first, I thought this was what formsets were for, but I can't see any way to automatically iterate over the contents of a database table to produce items to go in the form, if you see what I mean."
You need to get a queryset. And you need to provide that queryset to your form as initial data. See using initial data with a formset for the code.
initial = [ list of { dictionaries }, one per form ]
Interestingly, this is a direct feature of the model API through the values method of a queryset.
I have found my answer, using modelformset_factory instead formset_factory solves the problem, Thanks...