Edit:
My goal is to create a small e-commerce.
In my index I have a list of products, one of the attributes is a boolean called in_cart which states if the product is in the cart or not.
By default all boolean values are false. In my template is a table with all the products next to which I put a button "add to cart", which redirects to the cart template. However when I click on add to cart, the value of the boolean in question does not change to true. Any thoughts?
<table>
<tr>
<th>List of car parts available:</th>
</tr>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
{% for product in products_list %}
<tr>
<td>{{ product.id }}</td>
<td>{{ product.name }}</td>
<td>${{ product.price }}</td>
<td>{% if not product.in_cart %}
<form action="{% url 'add_to_cart' product_id=product.id %}" method="POST">
{% csrf_token %}
<input type="submit" id="{{ button_id }}" value="Add to cart">
</form>
{% else %}
{{ print }}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
See cart
And these are my views:
def index(request):
if request.method == "GET":
products_list = Product.objects.all()
template = loader.get_template('products/index.html')
context = {'products_list': products_list}
return HttpResponse(template.render(context, request))
return HttpResponse('Method not allowed', status=405)
def cart(request):
cart_list = Product.objects.filter(in_cart = True)
template_cart = loader.get_template('cart/cart.html')
context = {'cart_list': cart_list}
return HttpResponse(template_cart.render(context, request))
def add_to_cart(request, product_id):
if request.method == 'POST':
try:
product = Product.objects.get(pk=product_id)
product.in_cart = True
product.save()
except Product.DoesNotExist:
return HttpResponse('Product not found', status=404)
except Exception:
return HttpResponse('Internal Error', status=500)
return HttpResponse('Method not allowed', status=405)
Model:
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.IntegerField()
in_cart = models.BooleanField(default=False)
ordered = models.BooleanField(default=False)
def __str__(self):
return self.name
URLs
urlpatterns = [
path('', views.index, name='index'),
path('cart/', views.cart, name='cart')
re_path(r'^add_to_cart/(?P<product_id>[0-9]+)$', views.add_to_cart, name='add_to_cart')
]
Error in my terminal
File "/Users/Nicolas/code/nicobarakat/labelachallenge/products/urls.py", line 8
re_path(r'^add_to_cart/(?P<product_id>[0-9]+)$', views.add_to_cart, name='add_to_cart')
^
SyntaxError: invalid syntax
This is what it looks like in localhost:
First of all, your if statement is not reachable. Because you had a return before it. When you call return in a function, the next lines of function those are after the return will not execute.
So you should change the index function.
Also, you should send an identifier of the product with your post request. Identifier could be the id or any other unique field in your model.
So your code should be something like this:
def index(request):
if request.method == 'POST': # Request is post and you want to update a product.
try:
product = Product.objects.get(unique_field=request.POST.get("identifier")) # You should chnage `unique_field` with your unique filed name in the model and change `identifier` with the product identifier name in your form.
product.in_cart = True
product.save()
return HttpResponse('', status=200)
except Product.DoesNotExist: # There is no product with that identifier in your database. So a 404 response should return.
return HttpResponse('Product not found', status=404)
except Exception: # Other exceptions happened while you trying saving your model. you can add mor specific exception handeling codes here.
return HttpResponse('Internal Error', status=500)
elif request.method == "GET": # Request is get and you want to render template.
products_list = Product.objects.all()
template = loader.get_template('products/index.html')
context = {'products_list': products_list}
return HttpResponse(template.render(context, request))
return HttpResponse('Method not allowed', status=405) # Request is not POST or GET, So we should not allow it.
I added all information you need in the comments of the code. I think you should spend more time on python and django documents. But if you still have any questions, you can ask in comments.
After question edit
If you don't want to use a readonly field in your form, you should make two changes in your code.
First of all, you should add a url with product_id parameter in your urls.py file. Something like this:
url(r'^add_to_cart/(?P<product_id>[0-9]+)$', 'add_to_cart_view', name='add_to_cart')
Then you should add separate your add_to_cart view from index view. Your views should be like this:
def index(request):
if request.method == "GET":
products_list = Product.objects.all()
template = loader.get_template('products/index.html')
context = {'products_list': products_list}
return HttpResponse(template.render(context, request))
return HttpResponse('Method not allowed', status=405)
def cart(request):
cart_list = Product.objects.filter(in_cart = True)
template_cart = loader.get_template('cart/cart.html')
context = {'cart_list': cart_list}
return HttpResponse(template_cart.render(context, request))
def add_to_cart(request, product_id):
if request.method == 'POST':
try:
product = Product.objects.get(pk=product_id)
product.in_cart = True
product.save()
return HttpResponse('', status=200)
except Product.DoesNotExist:
return HttpResponse('Product not found', status=404)
except Exception:
return HttpResponse('Internal Error', status=500)
return HttpResponse('Method not allowed', status=405)
Now you should change the action link of your form to this:
{% url 'add_to_cart' product_id=product.id %}
Related
I want to show one button if a user has already added an object, and another button if he/she hasn't.
In my template I have:
<tr>
{% for word in dict_list %}
<td>{{word.target_word}} </td>
<td>{{word.source_word}}</td>
<td>
Add
{% if user_word %}
<a href="" class="add-word btn btn-success btn-sm" >Added</a>
{% endif %}
</td>
</tr>
{% endfor %}
And in my views:
def custom_create_word(request, object):
if request.method == 'POST':
pass
if request.method =="GET":
from .forms import WordForm
from .models import Word
word = Word.objects.get(pk=object)
user = request.user
target_word = word.target_word
source_word = word.source_word
deck_name = "My Words"
fluency = 0
new_word, created = Word.objects.get_or_create(user=user, target_word=target_word,
source_word=source_word, deck_name=deck_name, fluency=fluency)
return HttpResponseRedirect(reverse('vocab:dict'))
def get_context_data(self,**kwargs):
context = super(custom_create_word, self).get_context_data(**kwargs)
if Word.objects.filter(target_word=target_word, user=user).exists():
user_word == True
context['user_word'] = user_word
return context
I don't get any errors, but I don't get the desired result either. Am I going about it the wrong way?
Updated template:
<tbody>
<tr>
{% for word in dict_list %}
<td>{{word.target_word}}</td>
<td>{{word.source_word}}</td>
<td>
{% if user_word %}
<a href="" class="btn btn-success btn-sm" >Added</a>
{% else %}
Add
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
Updated views:
class Dictionary(FilterView):
model = Word
template_name = 'vocab/dictionary.html'
context_object_name = 'dict_list'
paginate_by = 15
filterset_class = WordFilter
strict = False
def get_queryset(self):
qs = self.model.objects.filter(user__username__iexact='special_user')
return qs
def get_object(self):
queryset = qs
pk = self.kwargs.get('pk')
if pk is None:
raise AttributeError('pk expected in url')
return get_object_or_404(queryset, pk=pk)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = WordFilter(self.request.GET, queryset=self.get_queryset())
word = Word.objects.get(pk=self.kwargs.get('pk'))
target_word = word.target_word
context['user_word'] = Word.objects.filter(target_word=target_word, user=self.request.user).exists()
return context
Update: If I hard code the pk in the above, I get the desired result, i.e. Add if word doesn't exist, and Added if it does. But the pk=self.kwargs.get('pk') doesn't work, I get DoesNotExist error - what should it be? How can I get access to each object's pk in the ListView?
urls.py:
app_name='vocab'
urlpatterns = [
path("index/",views.VocabHome.as_view(),name='index'),
path("list/", views.WordList.as_view(), name="list"),
path("create/", views.CreateWord.as_view(), name="create"),
re_path(r"by/(?P<username>[-\w]+)/(?P<pk>\d+)/$",views.WordDetail.as_view(),name="detail"),
re_path(r"delete/(?P<pk>\d+)/$",views.WordDelete.as_view(),name="delete"),
re_path(r"edit/(?P<pk>\d+)/$",views.WordUpdate.as_view(),name="edit"),
path('dictionary/', views.Dictionary.as_view(),name='dict'),
path("<int:object>/",views.custom_create_word,name="add-custom"),
]
I've also now tried the following:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = WordFilter(self.request.GET, queryset=self.get_queryset())
special_user_word = Word.objects.filter(user__username__iexact='special_user', target_word='target_word')
logged_user_word = Word.objects.filter(user=self.request.user, target_word='target_word')
user_word = None
if special_user_word == logged_user_word:
user_word = True
context['user_word'] = user_word
return context
But I get None everywhere... any thoughts?
Can you share the url's file, in the updated view i don't see how the function is getting called and which function is called.
It's user_word = True, not user_word == True.
Also in your template you should use the else clause otherwise when user_word is True you'll have 2 buttons.
Finally, def get_context_data(self,**kwargs): will not be executed because you are using a function as a view. get_context_data is for generic class based views. And even if it were executed, which I guess it's not, target_word that you use in it is not defined.
Update for you new problem
replace
word = Word.objects.get(pk=self.kwargs.get('pk'))
with
word = Word.objects.get(pk=kwargs.get('pk'))
So I am trying to allow users to edit menu item prices that are in the database currently. The fields will auto-populate data into the page and users will be able to edit that data to change the price. Here is what I have. I have tried and asked many questions, but I am still lost. I have googled a lot and it helped me understand forms a bit, but I'm not able to fix it. Please let me know if you need more info.
Views.py:
def edit_menu(request):
queryset = Product.objects.all()
context = { "object_list": queryset }
if request.method == 'POST':
post=ProductModelForm(request.POST)
if request.POST.get('price') and request.POST.get('name'):
if 'name' == Product.name:
post.name= request.POST.get('name')
post.price= request.POST.get('price')
post.save()
return redirect('Edit Menu Item')
else:
return redirect('Edit Menu Item')
else:
return render(request, 'mis446/edit-menu-item.html', context)
else:
return render(request, 'mis446/edit-menu-item.html', context)
forms.py:
class ProductModelForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name','price'] # specify which field you need to be in the form
HTML:
<title>ACRMS - Edit Menu Price</title>
<div class = "container">
<form action = "" method = 'POST'>
{% csrf_token %}
{% for instance in object_list %}
<input name = "name" value = "{{ instance.name }}"></input>
<input type="number" name="price" value = "{{ instance.price }}"/><br>
{% endfor %}
</select>
<button type ="submit">Submit Changes</button>
</form>
</div>
Urls.py:
url('edit-menu/edit/',views.edit_menu, name='Edit Menu Item'),
models.py:
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.IntegerField()
slug = models.SlugField()
def __str__(self):
return self.name
For your current implementation, you do not need a form. Instead, update the view like this:
# view
def edit_single_menu(request, pk):
if request.method == 'POST':
post=Product.objects.get(pk=pk)
if request.POST.get('price') and request.POST.get('name'):
post.name= request.POST.get('name')
post.price= request.POST.get('price')
post.save()
return redirect('Edit Menu Item')
else:
return redirect('Edit Menu Item')
return render(request, 'mis446/edit-menu-item.html', context)
# url
url('edit-menu/edit/<pk:id>/',views.edit_single_menu, name='edit_single_menu'),
# template (mis446/edit-menu-item.html)
<title>ACRMS - Edit Menu Price</title>
<div class = "container">
{% for instance in object_list %}
<form action = "{% url 'edit_single_menu' instance.pk %}" method = 'POST'>
{% csrf_token %}
<input name = "name" value = "{{ instance.name }}"></input>
<input type="number" name="price" value = "{{ instance.price }}"/><br>
<button type ="submit">Submit Changes</button>
</form>
{% endfor %}
</div>
Here I am sending individual edit to a new separated view named edit_single_menu and store the changes there.
Update
New url is not meant to replace the old one. It is only to assist you to update individual product. So, you need to keep both of the urls. Also, here is an answer based on #brunodesthuilliers's suggestion:
# view
def edit_single_menu(request, pk):
if request.method == 'POST':
post=Product.objects.get(pk=pk)
form = ProductForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect('Edit Menu Item')
Also, do some changes on edit_menu view:
def edit_menu(request):
queryset = Product.objects.all()
context = { "object_list": queryset }
return render(request, 'mis446/edit-menu-item.html', context)
And urls should look like this:
from django.urls import include, path
# rest of the code
path('edit-menu/edit/<int:pk>/',views.edit_single_menu, name='edit_single_menu'),
path('edit-menu/edit/',views.edit_menu, name='Edit Menu Item'),
I'm trying to displaying dictionary values in my template that i render from my views file. but its not displaying any value. In my html template show my just blank or not any error.
views.py
class BuyView(TemplateView):
template_name = 'shop/buy.html'
red_templateName = 'shop/receipt.html'
def get(self, request, product_id):
product = get_object_or_404(Product, pk=product_id)
args = {'product':product}
return render(request, self.template_name, args)
def post(self, request, product_id):
product = Product.objects.get(id=product_id)
if request.POST['address'] and request.POST['quantity']:
order = Order()
order.or_proName = product.pro_name
order.or_companyName = product.companyName
order.or_quatity = request.POST['quantity']
order.or_quatity = int( order.or_quatity)
orderPrice = order.or_quatity*product.Sale_Price
order.or_bill = 100 + orderPrice
order.pub_date = timezone.datetime.now()
product.Quantity -= order.or_quatity
product.save()
order = order.save()
args = {'order':order}
return render(request, self.red_templateName, args)
else:
return render(request, self.template_name, {'error':'Please Fill the address field'})
template.html
{% extends 'base.html' %}
{% block content %}
<div>
<div class="container p-5" style="border:1px solid gray;">
<small class="lead" >Your Order Receipt: </small>
{{ order.or_id }}
{{order.or_proName}}
{{order.or_companyName}}
{{order.or_quatity}}
{{order.pub_date}}
Deliever Chargers=100
-----------------------
Total = {{or_bill}}
</div>
</div>
{% endblock %}
Your template file name should match with following
red_templateName = 'shop/receipt.html'
I have this error [u'ManagementForm data is missing or has been tampered with'], even if the management_form is in the template, I'm using 2 forms in the template, I tried to put a prefix in the formset, but it doesn't accept prefix. This is my code. I don't know what I'm missing or if I need to make both of the forms, formsets. (First time working with formsets).
view.py
def DrugsPrescription(request, id):
data = RecipeDataForm(prefix='data')
drugs_formset = formset_factory(DrugsForm)
patient = PatientData.objects.get(pk=id)
errors = None
if request.method == 'POST':
data = RecipeDataForm(request.POST, prefix='data')
drugs_formset = drugs_formset(request.POST, prefix="med")
if data.is_valid():
info = data.save(commit=False)
info.patient = patient
if drugs_formset.is_valid():
info.save()
for form in drugs_formset.forms:
print form
med = drugs_formset.save(commit=False)
med.datos_id = info.pk
med.save()
success_url = reverse('/')
return HttpResponseRedirect(success_url)
else:
print drugs_formset.errors
else:
print data.errors
return render(
request, 'prescription.html',
{'data': data,
'patient': patient,
'drugs_formset': drugs_formset,
'errors': errors})
template.html
<form class="medical" id="drugs" method="POST" enctype="multipart/form-data">
{% crispy data %}
<div>{{errors}}</div>
<div id="data">
{{ drugs_formset.management_form }}
<table border="2">
{% for form in drugs_formset %}
<tr>
{{ form }}
</tr>
{% endfor %}
</table>
</div>
You have to instantiate your formsets also when the request is not POST.
Also avoid using the same variable for your formset class and formset instance.
Your code should rather be:
def drugs_prescription(request, id):
DrugsFormSet = formset_factory(DrugsForm)
patient = PatientData.objects.get(pk=id)
errors = None
if request.method == 'POST':
data = RecipeDataForm(request.POST, prefix='data')
drugs_formset = DrugsFormSet(request.POST, prefix="med")
if data.is_valid():
info = data.save(commit=False)
info.patient = patient
if drugs_formset.is_valid():
info.save()
for form in drugs_formset.forms:
print form
med = drugs_formset.save(commit=False)
med.datos_id = info.pk
med.save()
success_url = reverse('/')
return HttpResponseRedirect(success_url)
else:
print drugs_formset.errors
else:
print data.errors
else:
data = RecipeDataForm(prefix='data')
drugs_formset = DrugsFormSet(prefix='med')
return render(
request, 'prescription.html',
{'data': data,
'patient': patient,
'drugs_formset': drugs_formset,
'errors': errors})
I also renamed your function from DrugsPrescription to drugs_prescription to match python style conventions.
My aim is to use Two models in One template. I have tried various ways around this and have had no success. Originally I had 2 views, 2 models and Two forms. After searching I found people using inlineformsets. So I dropped One of the Views and set-up the inlineformset.
This is currently where I am up to and seem to be hitting a wall.
The template renders to the browser and the 'object_list' part displays the database content as desired and the 'form' part renders the form and validates/saves the data correctly. The issue is with the 'formset'. No fields are rendered (I would expect to see a dropdown as the field is a foreignkey) and when the 'submit' button is pressed I get:
AttributeError at /settings/
'NoneType' object has no attribute 'save'
Any help in finding the error or pointers on alternative solutions would be greatly appreciated.
The Code:
models.py
from django.db import models
class RevisionSettings(models.Model):
global_revision_type = models.CharField(max_length = 5, unique=True, blank = True)
global_revision_description = models.CharField(max_length = 300, unique=True, blank = True)
class Meta:
ordering = ["global_revision_type"]
def __unicode__(self):
return u'%s %s' % (self.global_revision_type, self.global_revision_description)
class RevisionDefaultType(models.Model):
defaultrevisiontype = models.ForeignKey(RevisionSettings)
class Meta:
ordering = ["defaultrevisiontype"]
def __unicode__(self):
return unicode(self.defaultrevisiontype)
views.py
class RevisionSettingsView(CreateView):
template_name = 'settings/revisionsettings_view.html'
model = RevisionSettings
form_class = SettingsForm
success_url = reverse_lazy('globalsettings')
success_message = 'Successfully added your new revision type'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SettingsFormSet(instance = RevisionSettings)
return self.render_to_response(
self.get_context_data(form=form,
formset=formset))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SettingsFormSet(self.request.POST)
if 'rev_settings_form_1' in self.request.POST:
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
elif 'rev_settings_form_2' in self.request.POST:
if formset.is_valid():
return self.formset_valid(formset)
else:
return self.form_invalid(formset)
def form_valid(self, form):
self.object = form.save()
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def formset_valid(self, formset):
self.object.save()
formset.instance = self.object
formset.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form,formset=formset))
def get_context_data(self, **kwargs):
kwargs['object_list'] = RevisionSettings.objects.order_by('global_revision_type')
return super(RevisionSettingsView, self).get_context_data(**kwargs)
forms.py
from django import forms
from django.forms.models import inlineformset_factory
from .models import RevisionSettings, RevisionDefaultType
class SettingsForm(forms.ModelForm):
class Meta:
model = RevisionSettings
class DefaultSettingsForm(forms.ModelForm):
class Meta:
model = RevisionDefaultType
SettingsFormSet = inlineformset_factory(RevisionSettings, RevisionDefaultType)
revisionsettings_view.html
(I have removed most of the HTML styling to keep the information to the point)
{% extends 'base_private.html' %}
{% block content %}
{% for object in object_list %}
<tr>
<td align="center">{{ object.global_revision_type }}</td>
<td align="center">{{ object.global_revision_description }}</td>
<td align="center"><span class="glyphicon glyphicon-remove-circle"></span></td>
</tr>
{% endfor %}
<form action = '{{ action }}' method = 'POST' class="form-horizontal" role="form">
{% csrf_token %}
<tr>
<td align="center">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
{{ form.defaultrevisiontype.label_tag }}
{{ form.defaultrevisiontype }}
{% endfor %}
</td>
</tr>
<span class="input-group-addon">
<input type = 'submit' name = 'rev_settings_form_2' value = 'Update Default Revision Type' class = 'btn btn-success'>
</span>
<td align="center">{{ form.global_revision_type }}{{ form.global_revision_type.errors }}</td>
<td align="center">{{ form.global_revision_description }}{{ form.global_revision_description.errors }}</td>
</tr>
<span class="input-group-addon">
<input type = 'submit' name = 'rev_settings_form_1' value = 'Add Revision Type' class = 'btn btn-success'>
</span>
</form>
{% endblock %}
Formsets are overkill for two forms. This is actually not too hard but poorly documented. You can make both forms the same form type, just give a prefix.
def parent_apply(request):
if request.method == 'POST':
parent_form = SignupForm(request.POST, prefix="parent")
student_form = StudentApplyForm(request.POST, prefix="student")
if parent_form.is_valid() and student_form.is_valid():
parent = parent_form.save()
student = student_form.save(parent)
else: messages.error(request, "Please correct the errors marked in red.")
else:
parent_form = SignupForm(prefix="parent")
student_form = StudentApplyForm(prefix="student")
return render_to_response('template_path/forms.html', { 'parent_form':parent_form, 'student_form':student_form }, context_instance=RequestContext(request))
The forms are just regular Django forms, no special settings required. You can change the order on which they validate and save one even if the other did not validate if you choose.
In your HTML Template, wrap both forms in the same tag and they will submit at the same time. If you want your forms to go to different view functions, specify two different elements.
Thanks for all the help. The pointers really helped me come to this solution. The main change was to 'def get' as shown below. I dropped the formset and passed the forms this way.
def get(self, request, *args, **kwargs):
form = self.settings_form_class
formset = self.default_form_class
return self.render_to_response(self.get_context_data(form = form, formset = formset))
I was unaware this was possible! Thanks again.