Whats the most Django/pythonic way to create or overwrite a record? - django-models

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.

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.

Related

django How do I get the user id in the data base with a foreign key

I'd like to get the user id in the db automatically when the user fill a form, but I don't know what I have to do. I'm working on that for all day long, I tried to do it by an hidden input but I'm not it's the solution.
This is my code:
class ImputationForm(forms.Form):
user = forms.ModelChoiceField(queryset=User.objects.all(), widget=forms.HiddenInput())
morning = forms.BooleanField( required=False)
afternoon = forms.BooleanField(required=False)
workpackage = forms.ModelChoiceField(
queryset=Workpackage.objects.all()
)
class ImputationFormModel(ModelForm):
class Meta:
model = Imputation
def imputation(request):
if request.method == 'POST': # If the form has been submitted...
form = ImputationForm(request.POST) # A form bound to the POST data
f = ImputationFormModel(request.POST)
if form.is_valid(): # All validation rules pass
user = request.user.id
morning = form.cleaned_data['morning']
afternoon = form.cleaned_data['afternoon']
workpackage = form.cleaned_data['workpackage']
f.save()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
else:
form = ImputationForm()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
Great question. Let's first start by identifying the purpose of various lines of code of your application and see if we can make sense of it. I see that you've got two forms right now which seems to reference the same kinds of data, which is a bit redundant. We'll want to avoid redundancy where possible, so let's start by perhaps cleaning up the code.
We can tell by the code you've shared that you're trying to:
Allow a user to submit a form with data regarding "morning", "afternoon", and "workpackage".
We're not checking whether or not you're editing an instance, so we can assume that upon successful form submission, a new Imputation record is created in the database with the data passed into it.
You're asking for a hidden input of a user value on the client side, which binds the submitted user ID to the form for adding a relationship with the user to the newly saved Imputation record.
Generally, you never want to allow hidden variables regarding sensitive or personal data that can be associated with a user on the client side. It's considered a huge security risk. A user may edit the hidden field using Firebug (or other DOM editing tools) and bind the user ID to a different user than which is intended.
With that being said, let's have a gander at the code now:
class ImputationForm(ModelForm):
class Meta:
model = Imputation
fields = [
'morning',
'afternoon',
'workpackage'
]
# assumed fields of morning, afternoon, workpackage
def imputation(request):
if request.method == 'POST': # If the form has been submitted...
form = ImputationForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
form.user = request.user
form.save()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
else:
form = ImputationForm()
return render_to_response( 'no/form.html', {'form' : form, }, context_instance=RequestContext(request))
With this code, we're no longer asking the user what his ID is on the form, and we're no longer even asking for it in the form altogether. We're allowing Django to determine what the current user ID is and pass it into the model right before saving the form. You'll want to make sure that the user field is set to null=True, blank=True on the model to avoid an exception from being raised.
I hope this helps answer your question.
I found it I'm really a newbie in django sorry for disturbing you and thank you very much for your help I would have not change my way of thinking on this issue without you!!!!! here is what I changed:
def imputation(request):
if request.method == 'POST': # If the form has been submitted...
form = ImputationForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
#print("%s" % form.user)
print("%s" % form)
new = form.save(commit=False) #here
new.user = request.user #here
new.save() #here
Thanks again ;)

Unique in ModelForm, how to get back to the form with error message?

I have found some posts about more or less the same situation like this one or this other one, but I was not able to adapt any of these to my situation.
What I would like is to return to my form with a warning if the user try to create another model with the same name of one already stored by himself in the db.
I would like to use Django built in facilities as described here, but I need some advices to change my code: could you help me, please?
My code follows:
models.py
class ShapeFile(models.Model):
name = models.CharField(max_length=100)
srid = models.ForeignKey(SpatialRefSys)
model_definition = models.OneToOneField(ModelDefinition)
user = models.ForeignKey(User)
color_table = models.ManyToManyField(ColorTable)
file = models.FileField(upload_to=get_upload_path)
class Meta:
unique_together = ('name', 'user')
forms.py
class UploadForm(ModelForm):
class Meta:
model = ShapeFile
fields = ('name','srid','file','color_table')
widgets = {'srid': TextInput()}
views.py
#login_required
def index(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
req = request.POST
sridVal = form.cleaned_data['srid'].pk
shpVal = form.cleaned_data['name']
# The final table name is something like 'mutant_celeryPy2_123_salzburg_lc'
end_table_name = request.user.username + "_" + shpVal + '_lc'
# Creates a table, otherwise return the retrieved one
model_def, created = ModelDefinition.objects.get_or_create(
app_label='celeryPy2',
object_name=end_table_name,
defaults=dict(
bases=(BaseDefinition(base=GeoModel),),
fields=(GeometryFieldDefinition(name='the_geom', srid=sridVal),
SmallIntegerFieldDefinition(name='cat'),)
)
)
obj = form.save(commit=False)
obj.user = request.user
obj.model_definition = model_def
obj.save()
messages.success(request, 'Shapefile upload succesful!')
return HttpResponse('Stored!')
else:
print "Upload shapefile form is invalid!!!"
else:
form = UploadForm()
return render_to_response('celeryPy2/index.html',
{'form': form,},
context_instance=RequestContext(request))
If I login as user1 and fill the form, let's suppose with name 'myshape', when I submit it I get the "Stored!" message: everything fine, model user1_myshape_lc is created.
If I re-login with the same user1 and try to store other data with field name set at 'myshape' as before I correctly get an exception:
Exception Type: IntegrityError at /celeryPy2/main
Exception Value: duplicate key value violates unique constraint "celeryPy2_shapefile_model_definition_id_key"
DETAIL: Key (model_definition_id)=(154) already exists.
How to get back my form with a warning instead to get the Django exception error message?
Thank you.
Model forms validation should take care of this and raise the validation error. But you have excluded the user field from form, which is part of unique_together constraint, hence its not getting verified. Ref validate_unique
You can try changing the view code to add user field in posted data and then instantiate the form.
Or better way would be add hidden user field in the form, so that its available in request.POST and the validation works as required.
Thanks Rohan, I found the hidden field solution the best: now I correctly get back my form with the error message: Shape file with this Name and User already exists.
Here's my modified code to get things work:
forms.py
class UploadForm(ModelForm):
class Meta:
model = ShapeFile
fields = ('name','user','srid','file','color_table')
widgets = {'srid': TextInput(), 'user': HiddenInput()}
views.py
#login_required
def index(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
req = request.POST
sridVal = form.cleaned_data['srid'].pk
shpVal = form.cleaned_data['name']
# The final table name is something like 'mutant_celeryPy2_123_salzburg_lc'
end_table_name = request.user.username + "_" + shpVal + '_lc'
# Creates a table, otherwise return the retrieved one
model_def, created = ModelDefinition.objects.get_or_create(
app_label='celeryPy2',
object_name=end_table_name,
defaults=dict(
bases=(BaseDefinition(base=GeoModel),),
fields=(GeometryFieldDefinition(name='the_geom', srid=sridVal),
SmallIntegerFieldDefinition(name='cat'),)
)
)
obj = form.save(commit=False)
obj.model_definition = model_def
obj.save()
messages.success(request, 'Shapefile upload succesful!')
return HttpResponse('Stored!')
else:
print "Upload shapefile form is invalid!!!"
else:
form = UploadForm(initial={'user': request.user})
return render_to_response('celeryPy2/index.html',
{'form': form,},
context_instance=RequestContext(request))

Tastypie filter by minimum value

I have a Django-tastypie resource that represents a banner and has a field called impression that I increment whenever the banner appears on the site.
class BannerResource(ModelResource):
owner = fields.ForeignKey('advertisment.api.AdvertiserResource', 'owner', full=True)
class Meta:
queryset = Banner.objects.all()
resource_name = 'banner'
authorization = Authorization()
I would like to get the banner that has the minimum impression, in the official documentation there is nothing like
filtering = {'impressions': ('min',)}
I'm using BackboneJS in the front end and I could get all the banners with Backbone collection and do the filtering with JavaScript but I'm looking for a quicker way to do it.
Any ideas?
Thanks
If you'd like to retrieve banners with number of impressions greater than X you need to things. For one you need to define possible filtering operations on your resource like so (given your model has impressions field):
class BannerResource(ModelResource):
owner = fields.ForeignKey('advertisment.api.AdvertiserResource', 'owner', full=True)
class Meta:
queryset = Banner.objects.all()
resource_name = 'banner'
authorization = Authorization()
filtering = { 'impressions' : ALL }
for available options take a look at Tastypie's documentation on filtering.
Then if you made the following request:
GET http://<your_host>/v1/banners?impressions__gte=X
you should get what you need.

Google App Engine + Validation

I am looking for how to do validation on Google App Engine and I have found only how to do it using Django framework. Ok Django approach is ok but if I have one form and this form have data from few tables what then???
I can not do it like this:
class Item(db.Model):
name = db.StringProperty()
quantity = db.IntegerProperty(default=1)
target_price = db.FloatProperty()
priority = db.StringProperty(default='Medium',choices=[
'High', 'Medium', 'Low'])
entry_time = db.DateTimeProperty(auto_now_add=True)
added_by = db.UserProperty()
class ItemForm(djangoforms.ModelForm):
class Meta:
model = Item
exclude = ['added_by']
class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<html><body>'
'<form method="POST" '
'action="/">'
'<table>')
# This generates our shopping list form and writes it in the response
self.response.out.write(ItemForm())
self.response.out.write('</table>'
'<input type="submit">'
'</form></body></html>')
def post(self):
data = ItemForm(data=self.request.POST)
if data.is_valid():
# Save the data, and redirect to the view page
entity = data.save(commit=False)
entity.added_by = users.get_current_user()
entity.put()
self.redirect('/items.html')
else:
# Reprint the form
self.response.out.write('<html><body>'
'<form method="POST" '
'action="/">'
'<table>')
self.response.out.write(data)
self.response.out.write('</table>'
'<input type="submit">'
'</form></body></html>')
Is any easy way to validate form which contain data from few tables or I have to code it alone?
Looks like you're using webapp; I suggest your look at some other 'light-weight' choices for form validation. Pick one that you like the layout / syntax of. You'll be able to define complex 'nested' relationships if needed.
FormEncode
Formish
Deform
WTForms has a GAE component WTForms
WTForms now includes support for AppEngine fields as well as auto-form generation. The form class can be used as it is or serve as a base for extended form classes, which can then mix non-model related fields, subforms with other model forms, among other possibilities.

django model/modelForm - How to get dynamic choices in choiceField?

i'm experimenting with django and the builtin admin interface.
I basically want to have a field that is a drop down in the admin UI. The drop down choices should be all the directories available in a specified directory.
If i define a field like this:
test_folder_list = models.FilePathField(path=/some/file/path)
it shows me all the files in the directory, but not the directories.
Does anyone know how i can display the folders?
also i tried doing
test_folder_list = models.charField(max_length=100, choices=SOME_LIST)
where SOME_LIST is a list i populate using some custom code to read the folders in a directory. This works but it doesn't refresh. i.e. the choice list is limited to a snapshot of whatever was there when running the app for the first time.
thanks in advance.
update:
after some thinking and research i discovered what i want may be to either
1. create my own widget that is based on forms.ChoiceField
or
2. pass my list of folders to the choice list when it is rendered to the client
for 1. i tried a custom widget.
my model looks like
class Test1(models.Model):
test_folder_ddl = models.CharField(max_length=100)
then this is my custom widget:
class FolderListDropDown(forms.Select):
def __init__(self, attrs=None, target_path):
target_folder = '/some/file/path'
dir_contents = os.listdir(target_folder)
directories = []
for item in dir_contents:
if os.path.isdir(''.join((target_folder,item,))):
directories.append((item, item),)
folder_list = tuple(directories)
super(FolderListDropDown, self).__init__(attrs=attrs, choices=folder_list)
then i did this in my modelForm
class test1Form(ModelForm):
test_folder_ddl = forms.CharField(widget=FolderListDropDown())
and it didn't seem to work.What i mean by that is django didn't want to use my widget and instead rendered the default textinput you get when you use a CharField.
for 2. I tried this in my ModelForm
class test1Form(ModelForm):
test_folder_ddl = forms.CharField(widget=FolderListDropDown())
test_folder_ddl.choices = {some list}
I also tried
class test1Form(ModelForm):
test_folder_ddl = forms.ChoiceField(choices={some list})
and it would still render the default char field widget.
Anyone know what i'm doing wrong?
Yay solved. after beating my head all day and going through all sorts of examples by people i got this to work.
basically i had the right idea with #2. The steps are
- Create a ModelForm of our model
- override the default form field user for a models.CharField. i.e. we want to explcitly say use a choiceField.
- Then we have to override how the form is instantiated so that we call the thing we want to use to generate our dynamic list of choices
- then in our ModelAdmin make sure we explicitly tell the admin to use our ModelForm
class Test1(models.Model):
test_folder_ddl = models.CharField(max_length=100)
class Test1Form(ModelForm):
test_folder_ddl = forms.choiceField()
def __init__(self, *args, **kwargs):
super(Test1Form, self).__init__(*args, **kwargs)
self.fields['test_folder_ddl'].choices = utility.get_folder_list()
class Test1Admin(admin.ModelAdmin):
form = Test1Form
I use a generator:
see git://gist.github.com/1118279.git
import pysvn
class SVNChoices(DynamicChoice):
"""
Generate a choice from somes files in a svn repo
""""
SVNPATH = 'http://xxxxx.com/svn/project/trunk/choices/'
def generate(self):
def get_login( realm, username, may_save ):
return True, 'XXX', 'xxxxx', True
client = pysvn.Client()
client.callback_get_login = get_login
return [os.path.basename(sql[0].repos_path) for sql in client.list(self.SVNPATH)[1:]]

Resources