2

Working with Django 1.2 I am making a wine review site. A user should only be able to review each wine once, but should be able to go back and re-review a wine without raising an error.

Using the get_or_create method seems the most rational solution but I have been running into various problems implementing it. Searching I found this article which looked promising: Correct way to use get_or_create?

and of course the django documentation on it: http://docs.djangoproject.com/en/1.2/ref/models/querysets/#get-or-create

But didn't seem to answer my question. Here is my code:

Views.py

@login_required
def wine_review_page(request, wine_id):
wine = get_object_or_404(Wine, pk=wine_id)

if request.method == 'POST':
form = WineReviewForm(request.POST)
if form.is_valid():
  review, created = Review.objects.get_or_create(
    user = request.user,
    wine = wine,
    like_dislike = form.cleaned_data['like_dislike'],
    ...
    )
variables = RequestContext(request, {
 'wine': wine
  })   
  review.save()
  return HttpResponseRedirect(
    '/detail/%s/' % wine_id
  )
else:
  form = WineReviewForm()
  variables = RequestContext(request, {
  'form': form,
  'wine': wine
 })
return render_to_response('wine_review_page.html', variables)

Models.py

class Review(models.Model):
  wine = models.ForeignKey(Wine, unique=True)
  user = models.ForeignKey(User, unique=True)
  like_dislike = models.CharField(max_length=7, unique=True)
  ...

If I understand how to use get_or_create correctly, since I am not matching on all the values like_dislike, etc... then django perceives it to be unique. I tried removing the other form parameters, but then they are not submitted with the post request.

Suggestions would be greatly appreciated.

1
  • Wouldn't you have problems with a unique like_dislike field? and shouldn't that just be a BooleanField that isn't unique?
    – dting
    Commented Apr 7, 2011 at 2:44

1 Answer 1

0

I came across this too when making a CRUD based app. I'm not sure if there's a better way but the way I ended up getting doing was using a exists() to check if an entry ... exists.

You can use get_or_create within the is_valid() scope, however, you need to check if the review exists before displaying your form in order to load instance data into the form in the case that the review already exists.

Your models.py might look like this:

from django.db import models
from django.contrib.auth.models import User

class Wine(models.Model):
    name = models.CharField()

class Review(models.Model):
    wine = models.ForeignKey(Wine)
    user = models.ForeignKey(User)
    like = models.BooleanField(null=True, blank=True) # if null, unrated

Your forms.py might look like this:

from django import forms

class WineReviewForm(forms.ModelForm):
    class Meta:
        model = Review
        fields = ['like',] # excludes the user and wine field from the form

Using get_or_create will let you do this if used like so:

@login_required
def wine_review_page(request, wine_id):
    wine = get_object_or_404(Wine, pk=wine_id)

    review, created = Review.objects.get_or_create(user=request.user, wine=wine)

    if request.method == 'POST':
        form = WineReviewForm(request.POST, instance=review)
        if form.is_valid():
            form.save()   
            return HttpResponseRedirect('/detail/%s/' % wine_id )
    else:
        form = WineReviewForm(instance=review)

    variables = RequestContext(request, {'form': form, 'wine': wine })
    return render_to_response('wine_review_page.html', variables) 

Doing creates a review just by visiting the page and requires that the other information either have a default or are allowed to be blank at the model level.

With exists(), you get two db hits if the review exists, however you don't create an object unless the user submits a valid form:

@login_required
def wine_review_page(request, wine_id):
    wine = get_object_or_404(Wine, pk=wine_id)

    review = None
    if Review.objects.filter(user=request.user, wine=wine).exists():
        review = Review.objects.get(user=request.user, wine=wine)

    if request.method == 'POST':
        form = WineReviewForm(request.POST, instance=review)
        if form.is_valid():
            form.save()   
            return HttpResponseRedirect('/detail/%s/' % wine_id )
    else:
        form = WineReviewForm(instance=review)

    variables = RequestContext(request, {'form': form, 'wine': wine })
    return render_to_response('wine_review_page.html', variables)

I used exists() but I think that this might be better?

try:
    review = Review.objects.get(user=request.user, wine=wine)
except Review.DoesNotExist:
    review = None

Hopefully someone with more experience will chime in.


Edit:

Here is a fairly old post from Daniel Roseman's Blog. I don't know if it is still applicable, but might be relevant to your question.

11
  • Thanks for the reply kriegar! I tried the exists code, but a get an error about the instance is equal to review: form = WineReviewForm(instance=review), Thoughts?
    – bmartinek
    Commented Apr 7, 2011 at 3:47
  • what's the error? That should work, WineReviewForm is a ModelForm for Review?
    – dting
    Commented Apr 7, 2011 at 4:01
  • Error: __init__() got an unexpected keyword argument 'instance'
    – bmartinek
    Commented Apr 7, 2011 at 4:50
  • 1
    It looks like you are using a Form not a ModelForm. docs.djangoproject.com/en/dev/topics/forms/modelforms
    – dting
    Commented Apr 7, 2011 at 4:57
  • Hmm never have used a ModelForm before. Will have to check it out.
    – bmartinek
    Commented Apr 7, 2011 at 5:06

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.