I'd like to be able to save data submitted by the form based on an argument passed via the url that references the product (instead of having the user to specify the product through a dropdown). Any thoughts on how I can accomplish this?
url(r'^products/(?P<product_id>\d+)/reviews/$', 'view_reviews'),
url(r'^products/(?P<product_id>\d+)/add_review/$', 'add_review'),
def add_review(request, product_id):
p = get_object_or_404(Productbackup, pk=product_id)
if request.method == 'POST':
form = ReviewbackupForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('reserve.views.view_reviews', kwargs={'product_id':p.id}))
else:
form = ReviewbackupForm()
variables = RequestContext(request, {'form': form, 'product_id': product_id})
return render_to_response('reserve/templates/create_review.html', variables)
RATING_OPTIONS = (
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, '5'),
(6, '6'),
(7, '7'),
(8, '8'),
(9, '9'),
(10, '10'),
)
class Reviewbackup(models.Model):
review = models.CharField('Review', max_length = 2000)
date = models.DateField('date')
created_on = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
user = models.CharField('Username', max_length = 200)
rating = models.IntegerField(max_length=2, choices=RATING_OPTIONS)
product = models.ForeignKey(Productbackup)
def __unicode__(self):
return self.review
class ReviewbackupForm(ModelForm):
class Meta:
model = Reviewbackup
fields = ('review', 'rating', 'user', 'date')
widgets = {
'review': Textarea(attrs={'cols': 80, 'rows': 7}),
}
class Productbackup(models.Model):
website = models.CharField('Product name', max_length = 200)
website_url = models.URLField('Product URL')
category = models.ForeignKey(Categories)
created_on = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.website
You can make use of form.save(commit=False), and set other attributes of your model in the instance and save it again.
In your view, when you save the form:
if form.is_valid():
review = form.save(commit=False)
review.product = p
review.save()
Related
I have 3 tables Student, K8Points, and TeacherClass . The table students tracks all student data information and you can assign them to a classroom, points tracks the points they earned for the day, classroom table is where you create the classroom names. What i want to accomplish is when you are logged in to /k8_points_classroom/1? page, there is a drop down field of students. 1 at the end of the classroom is the classroom id. The field student_name should only show the kids in that particular class, how do i filter that field ? Right now it just pulls up every student in the database. Thanks for the help .
Model
class TeacherClass(models.Model):
class_name = models.CharField(max_length = 50, default= "")
teacher_name = models.ManyToManyField(User)
grade = models.CharField(max_length = 2, default= "")
class Meta:
verbose_name = "Teacher Class Room"
def __str__(self):
return self.class_name
class Student(models.Model):
studentpsid= models.CharField(primary_key = True , default = "", max_length = 50, unique = True)
student_name = models.CharField(max_length = 50)
student_grade = models.CharField(max_length = 2, default = "")
home_room = models.CharField(max_length = 3, default = "")
counseling_goal = models.TextField(max_length = 255)
class_name = models.ManyToManyField(TeacherClass)
image =models.ImageField(default ="default.png", upload_to='student_pics')
class K8Points(models.Model):
date = models.DateField(default=datetime.date.today)
class_name = models.ForeignKey(TeacherClass, on_delete = models.PROTECT, default = "",)
student_name = models.ForeignKey(Student,on_delete = models.CASCADE, default ="" ,)
week_of = models.IntegerField(default=weeknumber)
day = models.CharField(max_length= 10, default = dayofweek)
TIME_FRAME_CHOICES = [
(None, 'PLEASE SELECT TIME FRAME'),
(1, '(1.) 8:45AM - 9:00AM'),
(2, '(2.) 9:00AM - 9:30AM'),
(3, '(3.) 9:30AM - 10:00AM'),
(4, '(4.) REC. I 10:00AM -10:10AM'),
(5, '(5.) 10:10AM-10:40AM'),
(6, '(6.) 10:40AM-11:10AM'),
(7, '(7.) 11:10AM-11:40AM'),
(8, '(8.) LUNCH 11:40AM-12:00PM'),
(9, '(9.) REC. II 12:00PM-12:20PM'),
(10, '(10.) 12:20PM-12:50PM'),
(11,'(11.) 12:50PM-1:20PM'),
(12,'(12.) 1:20PM-1:50PM'),
(13,'(13.) 1:50PM-2:20PM'),
(14,'(14.) REC. III 2:20PM-2:30PM'),
]
time_frame = models.PositiveSmallIntegerField(choices=TIME_FRAME_CHOICES,)
behavior = models.IntegerField(default="", validators=[
MaxValueValidator(5),
MinValueValidator(1)
])
academic = models.IntegerField(default="", validators=[
MaxValueValidator(5),
MinValueValidator(0)
] )
created_at = models.DateTimeField(default = timezone.now)
class Meta:
verbose_name = "K8-Points"
def __str__(self):
return self.student_name
Form
def __init__(self, *args, **kwargs):
self.classid = kwargs.pop('classid', None)
super(K8Points_ClassroomForm,self).__init__(*args,**kwargs)
self.fields['date'].disabled = True
self.fields['day'].disabled = True
self.fields['week_of'].disabled = True
self.fields['class_name'].widget.attrs['readonly'] = True
getstudents = Student.objects.filter(class_name = self.classid).order_by('student_name')
self.fields['student_name'].queryset= getstudents.all().order_by('student_name')
View
#login_required
def K8_Points_Classroom(request, classid):
if request.method == "GET":
date = datetime.date.today()
students = Student.objects.filter(class_name=classid).order_by('student_name')
class_name = TeacherClass.objects.get(id=classid)
form = K8Points_ClassroomForm(classid = class_name.id)
request.session['my_class_id'] = classid
return render(request, 'points/k8_points_classroom.html', {'form': form, 'students': students, 'class_name': class_name, 'date': date , 'classid': classid})
You need to pass the class in as a parameter to the form in the view, then adjust the student_name field's queryset to filter down to that class' students.
class K8PointsClassroomForm(forms.ModelForm):
def __init__(self, *args, class_=None, **kwargs):
super (K8Points_ClassroomForm,self).__init__(*args,**kwargs )
self.fields['date'].disabled = True
self.fields['day'].disabled = True
self.fields['week_of'].disabled = True
# Limit the fields queryset to that of the class's students.
self.fields['student_name'].queryset = class_.student_set.all()
Edit:
Here's the view I was trying to explain:
#login_required
def K8_Points_Classroom(request, classid):
class_name = TeacherClass.objects.get(id=classid)
form = K8Points_ClassroomForm(class_=class_name)
I have two filters in my code here, students filters everyone in that particular class and students_wr is another filter that quieres my table K8Recess which logs all the students in the school who got recess for that day. What I want to do is combine these filters.
So show everyone in my class who got recess for the day . How do i do that ? Here is my Code
def K8_Recess_Report(request, classid):
if request.method == "GET":
date = datetime.date.today()
class_name = TeacherClass.objects.get(id=classid)
getstudents = Student.objects.filter(class_name=classid)
students = getstudents.all().order_by('student_name')
students_wr = K8Recess.objects.filter(created_at__date = date )
my_class_id = request.session['my_class_id']
context = ({'students': students, 'class_name': class_name, 'my_class_id': my_class_id, 'date': date,})
return render(request, 'points/k8_recess_report.html', context)
class K8Points(models.Model):
date = models.DateField(default=datetime.date.today())
class_name = models.ForeignKey(TeacherClass, on_delete = models.PROTECT, default = "",)
student_name = models.ForeignKey(Student,on_delete = models.CASCADE, default ="" ,)
week_of = models.IntegerField(default=weeknumber)
day = models.CharField(max_length= 10, default = dayofweek)
TIME_FRAME_CHOICES = [
(None, 'PLEASE SELECT TIME FRAME'), # THIS IS OPTIONAL
(1, '(1.) 8:45AM - 9:00AM'),
(2, '(2.) 9:00AM - 9:30AM'),
(3, '(3.) 9:30AM - 10:00AM'),
(4, '(4.) REC. I 10:00AM -10:10AM'),
(5, '(5.) 10:10AM-10:40AM'),
(6, '(6.) 10:40AM-11:10AM'),
(7, '(7.) 11:10AM-11:40AM'),
(8, '(8.) LUNCH 11:40AM-12:00PM'),
(9, '(9.) REC. II 12:00PM-12:20PM'),
(10, '(10.) 12:20PM-12:50PM'),
(11,'(11.) 12:50PM-1:20PM'),
(12,'(12.) 1:20PM-1:50PM'),
(13,'(13.) 1:50PM-2:20PM'),
(14,'(14.) REC. III 2:20PM-2:30PM'),
]
time_frame = models.PositiveSmallIntegerField(choices=TIME_FRAME_CHOICES,)
behavior = models.IntegerField(default="", validators=[
MaxValueValidator(5),
MinValueValidator(1)
])
academic = models.IntegerField(default="", validators=[
MaxValueValidator(5),
MinValueValidator(0)
] )
created_at = models.DateTimeField(default=datetime.datetime.now())
class Meta:
verbose_name = "K8-Points"
def __str__(self):
return self.student_name
class K8Recess(models.Model):
student_ps = models.ForeignKey(Student,on_delete = models.CASCADE, default ="" ,)
created_at = models.DateTimeField(default=datetime.datetime.now())
morning_recess = models.BooleanField(blank= True, null = True)
lunch_recess = models.BooleanField(blank= True, null = True)
afternoon_recess = models.BooleanField(blank= True, null = True)
You can get this by:
K8Recess.objects.filter(student_ps__class_name=classid, created_at__date=date)
In the readerpage function, in my views.py, I am trying to calculate the avg of the two variables: readability_rating and actionability_rating, and store the result in avg_rating
def readerpage(request, content_id):
content = get_object_or_404(Content, pk=content_id)
form = ReviewForm(request.POST)
if form.is_valid():
review = form.save(commit=False)
review.content = content
readability_rating = form.cleaned_data['readability_rating']
readability = form.cleaned_data['readability']
actionability_rating = form.cleaned_data['actionability_rating']
actionability = form.cleaned_data['actionability']
general_comments = form.cleaned_data['general_comments']
review.avg_rating = (float(readability_rating) +
float(actionability_rating)) / 2
review.save()
return redirect('home')
args = {'content': content, 'form': form}
return render(request, 'content/readerpage.html', args)
The problem is that with this setup the two variables are still ChoiceFields - as such the above setup gives me the error:
float() argument must be a string or a number, not 'ChoiceField'
I’ve tried converting them to floats without any luck.
I also attempted using the TypedChoiceField with coerce=float, still with no luck
I’m not sure whether the best place to calculate this is in my function, my form, or my model?
models.py:
class Review(models.Model):
content = models.ForeignKey(Content, null=True, on_delete=models.CASCADE)
readability = models.CharField(null=True, max_length=500)
readability_rating = models.IntegerField(null=True)
actionability = models.CharField(null=True, max_length=500)
actionability_rating = models.IntegerField(null=True)
general_comments = models.CharField(null=True, max_length=500)
avg_rating = models.FloatField(null=True)
def _str_(self):
return self.title
forms.py:
class ReviewForm(forms.ModelForm):
readability = forms.CharField(widget=forms.Textarea)
readability_rating = forms.ChoiceField(
choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])
actionability = forms.CharField(widget=forms.Textarea)
actionability_rating = forms.ChoiceField(
choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])
general_comments = forms.CharField(widget=forms.Textarea)
class Meta:
model = Review
fields = ['readability', 'readability_rating',
'actionability', 'actionability_rating', 'general_comments']
Thanks for reading this.
The variables are ChoiceFields because you are declaring them as ChoiceFields in view function. Shouldn't you just fetch the values from your cleaned_data?
readability_rating = form.cleaned_data['readability_rating']
And to the second part of your question: Why not add it as a #property to your model?
I'm having an issue where I instantiate a ModelForm-based form with a pre-existing instance of the model in the GET portion of my view function. This model already has several fields filled in; the ModelForm is used to collect other fields in the model. The pre-existing fields are excluded in the ModelForm's definition.
The problem is, during POST processing, after successful validation of the ModelForm, I'm calling ModelForm.save(commit=False)...and the model returned (which should be the same one I passed in as 'instance' in the GET handling, remember) has somehow lost all the fields that were previously set. The fields actually set by the form are fine; but it's no longer the original instance of my model.
This is not the behavior I expected; and in fact I've used this partial-model-in-modelform previously, and it works other places. What am I missing here??
Hopefully some code'll make all this clear...
So here's the Model:
class Order(models.Model):
STATUS = (
('Created', -1),
('Pending', 0),
('Charged', 1),
('Credited', 2),
)
SHIPPING_STATUS = (
('Cancelled', 0),
('Ready for pickup', 1),
('Shipped', 2),
('OTC', 3),
)
orderID = models.IntegerField(max_length=15, null=True, blank=True)
store = models.ForeignKey(Store)
paymentInstrument = models.ForeignKey(PaymentInstrument, null=True, blank=True)
shippingMethod = models.ForeignKey(ShippingMethod, null=True, blank=True)
last_modified = models.DateTimeField(null=True, blank=True)
date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
total = models.FloatField(default=0.0, blank=True)
shippingCharge = models.FloatField(default=0.0, blank=True)
tax = models.FloatField(default=0.0, blank=True)
status = models.CharField(max_length=50, choices=STATUS, default = 'Created')
shippingStatus = models.CharField(max_length=50, choices=SHIPPING_STATUS, default = '1')
errstr = models.CharField(max_length=100, null=True, blank=True)
# billing info
billingFirstname = models.CharField(max_length = 50, blank = True)
billingLastname = models.CharField(max_length = 50, blank = True)
billingStreet_line1 = models.CharField(max_length = 100, blank = True)
billingStreet_line2 = models.CharField(max_length = 100, blank = True)
billingZipcode = models.CharField(max_length = 5, blank = True)
billingCity = models.CharField(max_length = 100, blank = True)
billingState = models.CharField(max_length = 100, blank = True)
billingCountry = models.CharField(max_length = 100, blank = True)
email = models.EmailField(max_length=100, blank = True)
phone = models.CharField(max_length=20, default='', null=True, blank=True)
shipToBillingAddress = models.BooleanField(default=False)
# shipping info
shippingFirstname = models.CharField(max_length = 50, blank = True)
shippingLastname = models.CharField(max_length = 50, blank = True)
shippingStreet_line1 = models.CharField(max_length = 100, blank = True)
shippingStreet_line2 = models.CharField(max_length = 100, blank = True)
shippingZipcode = models.CharField(max_length = 5, blank = True)
shippingCity = models.CharField(max_length = 100, blank = True)
shippingState = models.CharField(max_length = 100, blank = True)
shippingCountry = models.CharField(max_length = 100, blank = True)
Here's the ModelForm definition:
class OrderForm(ModelForm):
class Meta:
model = Order
exclude = ('orderID',
'store',
'shippingMethod',
'shippingStatus',
'paymentInstrument',
'last_modified',
'date',
'total',
'payportCharge',
'errstr',
'status', )
widgets = {
'billingCountry': Select(choices = COUNTRIES, attrs = {'size': "1"}),
'shippingCountry': Select(choices = COUNTRIES, attrs = {'size': "1"}),
'billingState': Select(choices = STATES, attrs = {'size': "1"}),
'shippingState': Select(choices = STATES, attrs = {'size': "1"}),
}
And here is the view function:
def checkout(request):
theDict = {}
store = request.session['currentStore']
cart = request.session.get('cart', False)
order = request.session['currentOrder'] # some fields already set
if not cart: # ...then we don't belong on this page.
return HttpResponseRedirect('/%s' % store.urlPrefix)
if request.method == 'GET':
form = OrderForm(instance=order, prefix='orderForm')
else: # request.method == 'POST':
logging.info("Processing POST data...")
form = OrderForm(request.POST, prefix='orderForm')
if form.is_valid():
### AT THIS POINT, ORDER FIELDS ARE STILL GOOD (I.E. FILLED IN)
order = form.save(commit=False)
### AFTER THE SAVE, WE'VE LOST PRE-EXISTING FIELDS; ONLY ONES SET ARE
### THOSE FILLED IN BY THE FORM.
chargeDict = store.calculateCharge(order, cart)
request.session['currentOrder'] = order
return HttpResponseRedirect('/%s/payment' % store.urlPrefix)
else:
logging.info("Form is NOT valid; errors:")
logging.info(form._errors)
messages.error(request, form._errors)
theDict['form'] = form
theDict['productMenu'] = buildCategoryList(store)
t = loader.get_template('checkout.html')
c = RequestContext(request, theDict)
return HttpResponse(t.render(c))
Any/all help appreciated...
When you instantiate the form during the POST, the instance of the model you're editing is None, because you're not passing it in, and you're not persisting the form instance from the GET. Django won't do anything on its own to persist data between requests for you.
Try:
...
form = OrderForm(request.POST or None, instance=order, prefix='orderForm')
if request.method == 'POST':
logging.info("Processing POST data...")
if form.is_valid():
...
Now the instance will be populated for GET and POST, but the data from request.POST is optional for the form if it's None. It also saves you from having to instantiate the form in two places depending on request.method
Given a product (product_name is a parameter in the view), I am trying to return the 5 top-ranked products within that category (as defined by the method "get_avg_rating") as a list that I can loop through in a template. Any advice on how to do this?
class Productbackup(models.Model):
website = models.CharField('Product name', max_length = 200)
url_friendly = models.CharField('URL friendly', max_length = 200)
website_url = models.URLField('Product URL')
description= models.CharField('Description', max_length = 2000)
category = models.ForeignKey(Categories)
#category = models.ManyToManyField(Categories)
image_hero = models.URLField('Hero image url')
image_second = models.URLField('Second image url')
image_third = models.URLField('Third image url')
created_on = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.website
def get_avg_rating(self):
reviews = Reviewbackup.objects.filter(product=self)
count = len(reviews)
sum = 0.0
for rvw in reviews:
sum += rvw.rating
return (sum/count)
def get_num_reviews(self):
reviews = Reviewbackup.objects.filter(product=self)
count = len(reviews)
return count
RATING_OPTIONS = (
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(5, '5'),
(6, '6'),
(7, '7'),
(8, '8'),
(9, '9'),
(10, '10'),
)
class Reviewbackup(models.Model):
review = models.CharField('Review', max_length = 2000)
created_on = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
user = models.CharField('Username', max_length = 200)
rating = models.IntegerField(max_length=2, choices=RATING_OPTIONS)
product = models.ForeignKey(Productbackup)
def __unicode__(self):
return self.review
class Categories(models.Model):
category = models.CharField('Category_second', max_length = 200)
url_friendly = models.CharField('url_friendly', max_length = 200)
def __unicode__(self):
return unicode(self.category)
def view_reviews(request, product_name):
product = get_object_or_404(Productbackup, url_friendly=product_name)
product_id = product.id
#get reviews for the this product
reviews = Reviewbackup.objects.filter(product_id=product_id).order_by("-created_on")
#similar products in category comparison
prod_category = Productbackup.objects.filter(category=product.category)
#top_ranked = Productbackup.objects.order_by('get_avg_rating')[0:5]
#recently added
recent_added = Productbackup.objects.order_by('-created_on')[0:5]
return render_to_response('reserve/templates/view_reviews.html', {'prod_category': prod_category, 'product':product, 'reviews':reviews, 'recent_added':recent_added},
context_instance=RequestContext(request))
You can use annotate for that
from django.db.models import Sum, Avg
def get_top_products(amount=5):
try:
Productbackup.objects.annotate(review_amount=Sum("reviewbackup__product")).\
order_by("review_amount")[:amount]
except Productbackup.DoesNotExist:
return None
This is just a basic example, which can be extended to your needs
Try to implement that:
Create a dictionary. It will have the Product as the key and the rating as the value.
Loop through your items (Product.objects)
In that loop, put each item in the dictionary
Out of that loop, sort the dictionary by value.
(See Sort a Python dictionary by value)
Get the last items of the dictionary (dictionary[-5]).
Note that you will have to handle items with ex-aequo ratings. Indeed, if 10 items have the same score, then your Top 5 doesn't mean anything using the above method.
This would result in a code similar to this:
items_with_rating = {}
for product in Product.objects:
items_with_rating[product] = product.get_avg_rating()
items_sorted_by_rating = sorted(items_with_rating.iteritems(), key=operator.itemgetter(1))
top5_items = items_sorted_by_rating[-5]
Maybe something like this could work?
products = [[prod, prod.get_avg_rating()] for prod in Productbackup.objects()]
top_5_items = sorted(products, key=lambda x: x[1], reverse=True)[:5]