None Type Error Attribute - django

I've been trying to implement a shopping cart on my website based on Django.
Below are the models I used:
class ShoppingCart(models.Model):
songs = models.ManyToManyField(Song)
albums = models.ManyToManyField(Album)
class Listener(User):
name = models.CharField(max_length=200, blank=True, null=True)
bio = models.TextField(blank=True, null=True)
cart = models.ForeignKey(ShoppingCart, blank=True, null=True)
Here is the views.py where I get an error saying None Type Object has no attribute songs at request.user.listener.cart.songs.add():
def add_to_cart(request):
if not request.user.is_authenticated() or not request.method == 'POST':
return HttpResponse(json.dumps({'success':False}), content_type='application/json')
typeobj = request.POST.get('type', None)
obj = request.POST.get('id', None)
if typeobj is None or obj is None:
return HttpResponse(json.dumps({'success':False}), content_type='application/json')
if typeobj == 'album':
try:
album = Album.objects.get(pk=obj)
except ObjectDoesNotExist:
return HttpResponse(json.dumps({'success':False}), content_type='application/json')
request.user.listener.cart.albums.add(Album.objects.get(pk=obj))
else:
try:
song = Song.objects.get(pk=obj)
except ObjectDoesNotExist:
return HttpResponse(json.dumps({'success':False}), content_type='application/json')
request.user.listener.cart.songs.add(Song.objects.get(pk=obj))
return HttpResponse(json.dumps({'success':True}), content_type='application/json')
I checked in the shell and the same error occurs when I try to add a song to the cart. It says cart is a NoneType object and has no attribute songs.
Thanks in advance.

I think you should use OneToOne relationship between User and ShoppingCart.
Sample model:
class Listener(User):
name = models.CharField(max_length=200, blank=True, null=True)
bio = models.TextField(blank=True, null=True)
cart = models.OneToOneField(ShoppingCart, blank=True, null=True)
In your view create a cart for user if it does not exists
As
def add_to_cart(request):
if not request.user.is_authenticated() or not request.method == 'POST':
return HttpResponse(json.dumps({'success':False}), content_type='application/json')
typeobj = request.POST.get('type', None)
obj = request.POST.get('id', None)
if typeobj is None or obj is None:
return HttpResponse(json.dumps({'success':False}), content_type='application/json')
if request.usr.listener.cart == None:
cart = ShoppingCart()
cart.save()
request.usr.listener.cart = cart
request.usr.listener.save()
# your code to add items in cart

request.user is an instance of the User object of the currently logged in user.
There is no relation between your Listener model and the User model (even though you inherited from it), so whatever you are trying to do, it won't work. In fact, even if there was a relationship, you'd be seeing these errors because you are not using the object relations correctly.
If you want to track a shopping cart for each user permanently, you need to add a ForeignKey to your ShoppingCart model to point to the User model.
If you just want to track the cart for a session; then use sessions to do so.
It might benefit you from going through the relations section of the documentation, since you aren't using add() correctly either.

Related

With Django, how do I reference an existing model in a form save method instead of creating a new instance?

I'm trying to use a ModelChoiceField to display options populated from model, and when a user selects a choice, store that method in a different model.
I'm using a standard form instead of a ModelForm, because I wasn't able to get the form to display how I wanted to when using a Modelform.
My issue is that in my form save method, a new instance is created, which is not what I want.
Here are the relevant models:
class Client(models.Model):
client_email = models.EmailField(max_length = 254)
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
phone = PhoneField(blank=True)
assigned_manager = models.ForeignKey(Manager, on_delete=models.CASCADE, blank=True, null=True)
created_date = models.DateTimeField(default=timezone.now)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
class Manager(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
manager_email = models.EmailField(max_length = 254)
username = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, blank=True, null=True)
#property
def full_name(self):
return '{0} {1}'.format(self.first_name, self.last_name)
My view:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
form = AssignManagerForm()
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
form.save()
return render(request, 'mysite/manageclient.html', {})
else:
form = AssignManagerForm()
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
And my forms.py
class AssignManagerForm(forms.Form):
full_name = forms.ModelChoiceField(queryset=Manager.objects.all())
def save(self):
data = self.cleaned_data
client = Client(assigned_manager=data['full_name'])
client.save()
What I need to do is pass the urlid in my view to my save method in my forms.py, but I am unsure how to do that. Even if i could do that, I'm not sure how to modify form save to use urlid to refer to a specific record and set only the assigned_manager record.
Additionally, while I want the meta field to be used to display the form, I know it isn't what should be being passed to the assigned_manager field. How would I pass a Manager of instance to establish the foreign key relationship?
edit: edited to correct queryset in forms.py as per comments
Here is a solution using a ModelForm, by using a ModelForm you no longer have to manually set attributes on save or provide initial values when updating an existing instance.
The field assigned_manager will still be named assigned_manager but it's label can be overridden to be whatever you want it to be by passing labels in the ModelForm.Meta
class AssignManagerForm(forms.ModelForm):
class Meta:
model = Client
fields = ['assigned_manager']
labels = {'assigned_manager': 'Full name'}
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST, instance=client)
if form.is_valid():
client = form.save()
# The general convention is to redirect after a successful POST
else:
form = AssignManagerForm(instance=client)
context = {
'client': client,
'urlid': urlid,
'form': form,
}
return render(request, 'mysite/manageclient.html', context)
Instead of saving it in form, you can directly do this operation in view. For example:
def manageclient(request, urlid):
client = Client.objects.get(id=urlid)
if request.method == "POST":
form = AssignManagerForm(request.POST)
if form.is_valid():
client.assigned_manager = form.cleaned_data['full_name']
client.save()
return render(request, 'mysite/manageclient.html', {})

Django. How to restrict a user to put review only once?

Hello guys I am making a ecommerce website as part of learning django. I have a review model but I want to make sure that a customer can only put a review once. How to do that with my existing model and views. Please have a look at my codes:
views
def product_review(request, slug):
user = request.user
product = get_object_or_404(Product, slug=slug)
reviews = ProductFeedback.objects.filter(product=product).order_by('-id')
if request.POST:
if product.user == request.user:
messages.error(request, 'You can\'t review your own product')
return redirect('store:product_detail', product.slug)
else:
p_review_form = ProductReviewForm(request.POST or None)
if p_review_form.is_valid():
product_rating = request.POST.get('product_rating')
product_review = request.POST.get('product_review')
p_review = ProductFeedback.objects.create(product=product, user=user,
product_rating=product_rating, product_review=product_review)
p_review.save()
return redirect('store:product_detail', product.slug)
else:
p_review_form = ProductReviewForm()
context = {
'product':product,
'p_review_form':p_review_form,
'reviews': reviews,
}
return render(request, 'store/product/product_detail.html', context)
model
class ProductFeedback(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='product')
product_rating = models.CharField(choices=RATINGS, max_length=10, null=True)
product_review = models.TextField(max_length=1000)
reply = models.ForeignKey('ProductFeedback', null=True, related_name='replies', blank=True, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'{self.user.username}--{self.product.title} review'
You can filter all reviews by the user if there is any review then raise an error. The main idea is to get the reviews and filter them by request.user and see if there are any.
from django.core.exceptions import PermissionDenied
def product_review(request, slug):
product = get_object_or_404(Product, slug=slug)
# Get the reviews posted by the user for this product
user_review = Product.product.filter(user=request.user)
if request.method == 'POST':
if user_review:
# If there is/are any reviews, raise an error
raise PermissionDenied('You have already given your review on this post.')
Hope this helps.

Use a different form based on variable

I have a "product" field that I want to use to determine which form to display. I am trying to do this in the view but wondering if I should do it in the template instead. I have tried the following but "form" does not get assigned by my if statements. What am I doing wrong?
#login_required
def update_message(request, pk):
message = get_object_or_404(Submission, pk=pk)
author = message.author
date_posted = message.date_posted
product = message.product
message_obj = Submission.objects.get(pk=pk)
program_type = message.program_type
if author == request.user:
if request.method == 'POST':
if product == 'Apple':
form = AppleForm(request.user, request.POST, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, request.POST, instance=message)
if form.is_valid():
message_sub = form.save(commit=False)
message_sub.author = request.user
message_sub.date_posted = timezone.now()
message_sub.save()
form.save_m2m()
messages.success(request, 'Message updated')
return redirect('submission-list')
else:
if product == 'Apple':
form = AppleForm(request.user, instance=message)
if product == 'Orange':
form = OrangeForm(request.user, instance=message)
else:
messages.warning(request, 'You can't do that.')
return redirect('message-submission-list')
return render(request, 'programs/submission_create_form.html', {'product':product,'form': form, 'message_obj': message_obj,'program_type':program_type})
class MessageSubmission(models.Model):
message = models.CharField(max_length=5000)
author = models.ForeignKey(User, on_delete=models.CASCADE)
date_posted = models.DateTimeField(default=timezone.now)
program_code = models.ManyToManyField(Program)
program_type = models.CharField(max_length=30, blank=True)
product = models.ForeignKey('Product', on_delete=models.SET_NULL, null=True)
production_cycle = models.ManyToManyField('ProductionCycle', null=True)
def get_absolute_url(self):
return reverse('submission-list')
def __str__(self):
return self.message
As I mentioned in the comment, the issue is that product is a ForeignKey to another model. In the template, the FK will display using the __str__ method of that model, but that doesn't make it equal to that display value. You should compare explicitly with the relevant field on the target model:
if product.fruit_type == 'Orange' # or whatever the field is
(Alternatively you could do if str(product) == 'Orange' but that's more brittle and is coupling display logic in a way that's not very nice.)
There's nothing wrong with doing this in the views. If the form is not defined after those if statements then it means that the value of product is not Apple or Orange, but something else. I would double check the value of product to fix the issue.
Since Product is a class, you should reference a field. You didn't post the code for it, but for example
if form == product.name
If there is a name field.

Django forms: how to show only objects associated with user in dropdown

I am a vet hospital, with class Pet and class Records. Each pet can have many records, i.e. everytime it visits the hospital it gets a new record.
At the moment, my form shows all the pets ever associated with my app (please view https://i.stack.imgur.com/8j7V8.png).
I want only the user's registered pets to appear (why would Bob be bringing a stranger's cat to the vet?)
View to add a record:
#login_required(login_url="/accounts/login/")
def record_create(request):
#this line retrieves Pets only belonging to the user logged in
pets = Pet.objects.filter(author=request.user)
if request.method == 'POST':
form = forms.CreateRecord(request.POST, request.FILES)
print(form)
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return redirect('records')
else:
form = forms.CreateRecord()
return render(request, 'records/record_create.html', {'form': form,})
forms.py
class CreateRecord(forms.ModelForm):
class Meta:
model = models.Record
fields = ['feedID', 'amountLeftOver', 'amountDispensed', 'additionalInfo', 'selectPet']
models.py
class Pet(models.Model):
petName = models.CharField(max_length=100, default='My Pet')
petImage = models.ImageField(default='default.png', blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
def __str__(self):
return self.petName
class Record(models.Model):
feedID = models.AutoField(primary_key=True)
dateTime = models.DateTimeField(auto_now_add=True)
amountLeftOver = models.IntegerField(default='0')
amountDispensed = models.IntegerField(default='0')
additionalInfo = models.TextField(default=" ")
selectPet = models.ForeignKey(Pet, on_delete=models.CASCADE)
author = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
How do I get the selectPet dropdown to show only Bob's registered pets?
Thanks for your time!
You can override the __init__() method of your form to pass in extra arguments, in this case, you can pass in a user instance, and set the queryset for the dropdown widget.
(Not sure about how you have related the Pet model to the User model, the example below assumes you have a foreign key)
class CreateRecord(forms.ModelForm):
class Meta:
model = models.record
fields = ['feedID', 'amountLeftOver', 'amountDispensed', 'additionalInfo', 'selectPet']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
user = self.kwargs.get('user')
if user:
self.fields['selectPet'].queryset = user.pet_set.all()
Another way to resolve this problem
View to add a record:
#login_required(login_url="/accounts/login/")
def record_create(request):
#this line retrieves Pets only belonging to the user logged in
pets = Pet.objects.filter(author=request.user)
if request.method == 'POST':
form = forms.CreateRecord(request.POST, request.FILES)
print(form)
if form.is_valid():
instance = form.save(commit=False)
instance.author = request.user
instance.save()
return redirect('records')
else:
form = forms.CreateRecord()
form.fields["category"].queryset=Record.objects.filter(user=request.user)
return render(request, 'records/record_create.html', {'form': form,})

Add a Manytomany item in a list after a request

I need to make an automatic add in a ManyToMany field. My Class :
class UserProfile(models.Model):
user = models.OneToOneField(User, unique=True)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='student')
courses_list = models.ManyToManyField(Course, blank=True)
After saving a new Course I want to add it to course_list of the user :
def newcourse(request):
if not request.user.is_authenticated():
return render_to_response('login.html')
form = CourseForm()
if request.method == 'POST':
form = CourseForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.owner = request.user
obj = form.save()
course_list = request.user.userprofile.courses_list.all()
course_list += form
course_list.save()
return render(request, 'mycourses.html')
return render(request, 'newcourse.html', locals())
But it doesn't works : `unsupported operand type(s) for +=: 'ManyRelatedManager' and 'CourseForm'``
Maybe I need to make an new request ?
If you have an idea.. :D
You need to do the following:
request.user.userprofile.courses_list.add(obj)
See the docs on ManyToMany relationships for more detail:
https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/
Of course, you should probably handle getting the profile in the "proper" way as well:
try:
profile = request.user.get_profile()
profile.courses_list.add(obj)
except UserProfile.DoesNotExist:
messages.error(request, "Couldn't find profile")