Learning how to use Django. I'm trying this code:
if form.is_valid() and form.has_changed():
try:
p = profile.objects.get(user_id= user.id)
#Append data to profile record
profile_record.save()
except p.DoesNotExist as e:
#Create new profile record
profile_record.save()
When user_id (FK field) isn't found I get an error:
local variable 'p' referenced before assignment
I've assumed that p = ... is the assignment?
Thank you for any pointers as to what I'm doing wrong here.
Your error is occurring because p is first set during the try section. On except, you reference p, but p has not been assigned to anything at that point, because if the except is reached, then the try section is "undone" and that is where p is assigned.
Try changing except p.DoesNotExist as e: to except Person.DoesNotExist:
Maybe you could use get_or_create https://docs.djangoproject.com/en/1.8/ref/models/querysets/#django.db.models.query.QuerySet.get_or_create
p, created = profile.objects.get_or_create(user_id=user.id)
Try to filter the query.. .get should always be used in case you know there is going to only one object while querying.
if form.is_valid() and form.has_changed():
p = profile.objects.filter(user_id = user.id)
if p:
p = p[0]
#Append data to profile record
profile_record.save()
else:
#Create new profile record
profile_record.save()
.get(field=something) will always throw error if there is no result or more than 1 result, whereas .filter() does not throw error in these cases.
Why not using
if form.is_valid() and form.has_changed():
form.save()
Related
Before updating, I am running a query to get the town i want to update using an id. Now my assumptions are if the id does not exist I am supposed to have an error, but I am getting none.
def update_town(session, town_dict):
try:
town = session.query(Town).where(Town.id == town_dict["id"] )
print(town)
#setting new values
town.update(town_dict)
session.commit()
except NoResultFound:
return "not results found", 404
return "ok"
to check if this id exists, you could use .first()
town = session.query(Town).where(Town.id == town_dict["id"] ).first()
from the docs:
Return the first result of this Query or None if the result doesn’t contain any row.
you can then do a check, and only update town if this id exists:
if town:
town.update()
session.commit()
return "ok"
else:
return "not results found", 404
.first() is only one of many approaches for this, but it's pretty simple in practice. I wouldnt use a try-except block unless that's for some reason your preferred way
I use SQLFORM.smartgrid to show a list of records from a table (service_types). In each row of the smartgrid there is a delete link/button to delete the record. I want to executive some code before smartgrid/web2py actually deletes the record, for example I want to know if there are child records (services table) referencing this record, and if any, flash a message telling user that record cannot be deleted. How is this done?
db.py
db.define_table('service_types',
Field('type_name', requires=[IS_NOT_EMPTY(), IS_ALPHANUMERIC()]),
format='%(type_name)s',
)
db.define_table('services',
Field('service_name',requires=[IS_NOT_EMPTY(),IS_NOT_IN_DB(db,'services.service_name')]),
Field('service_type','reference service_types',requires=IS_IN_DB(db,db.service_types.id,
'%(type_name)s',
error_message='not in table',
zero=None),
ondelete='RESTRICT',
),
Field('interest_rate','decimal(15,2)',requires=IS_DECIMAL_IN_RANGE(0,100)),
Field('max_term','integer'),
auth.signature,
format='%(service_name)s',
)
db.services._plural='Services'
db.services._singular='Service'
if db(db.service_types).count() < 1:
db.service_types.insert(type_name='Loan')
db.service_types.insert(type_name='Contribution')
db.service_types.insert(type_name='Other')
controller
def list_services():
grid = SQLFORM.smartgrid(db.services
, fields = [db.services.service_name,db.services.service_type]
)
return locals()
view
{{extend 'layout.html'}}
{{=grid}}
There are two options. First, the deletable argument can be a function that takes the Row object of a given record and returns True or False to indicate whether the record is deletable. If it returns False, the "Delete" button will not be shown for that record, nor the delete operation be allowed on the server.
def can_delete(row):
return True if [some condition involving row] else False
grid = SQLFORM.smartgrid(..., deletable=can_delete)
Second, there is an ondelete argument that takes the db Table object and the record ID. It is called right before the delete operation, so to prevent the delete, you can do a redirect within that function:
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
session.flash = 'Cannot delete this record'
redirect(URL())
grid = SQLFORM.smartgrid(..., ondelete=ondelete)
Note, if the grid is loaded via an Ajax component and its actions are therefore performed via Ajax, using redirect within the ondelete method as shown above will not work well, as the redirect will have no effect and the table row will still be deleted from the grid in the browser (even though the database record was not deleted). In that case, an alternative approach is to return a non-200 HTTP response to the browser, which will prevent the client-side Javascript from deleting the row from the table (the delete happens only on success of the Ajax request). We should also set response.flash instead of session.flash (because we are not redirecting/reloading the whole page):
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
response.flash = 'Cannot delete this record'
raise HTTP(403)
Note, both the deletable and ondelete arguments can be dictionaries with table names as keys, so you can specify different values for different tables that might be linked from the smartgrid.
Finally, notice the delete URLs look like /appname/list_services/services/delete/services/[record ID]. So, in the controller, you can determine if a delete is being requested by checking if 'delete' in request.args. In that case, request.args[-2:] represents the table name and record ID, which you can use to do any checks.
From Anthony's answer I chose the second option and came up with the following:
def ondelete_service_type(service_type_table, service_type_id):
count = db(db.services.service_type == service_type_id).count()
if count > 0:
session.flash = T("Cant delete")
#redirect(URL('default','list_service_types#'))
else:
pass
return locals()
def list_service_types():
grid = SQLFORM.smartgrid(db.service_types
, fields = [db.service_types.type_name, db.services.service_name]
, ondelete = ondelete_service_type
)
return locals()
But, if I do this...
if count > 0:
session.flash = T("Cant delete")
else:
pass
return locals()
I get this error:
And if I do this:
if count > 0:
session.flash = T("Cant delete")
redirect(URL('default','list_service_types#')) <== please take note
else:
pass
return locals()
I get the flash error message Cant delete but the record appears deleted from the list, and reappears after a page refresh with F5 (apparently because the delete was not allowed in the database, which is intended).
Which one should I fix and how?
Note
If any of these issue is resolved I can accept Anthony's answer.
my first question here, perhaps a noob question but I really don't understand what I'm doing wrong, and I can't find any clue on the web.py documentation.
Is it possible to use a db select to validate a field?
What I'm doing:
I'm building a registration form, I'm having trouble with the username validation.
In every example I found, users are declared in a variable before the registration Class with code like this:
allowed = (
('jon','pass1'),
('tom','pass2')
)
and used in the validation like this:
form.Validator('Username already exists.', lambda x: x not in allowed)
Since I'm saving in db, I can change the allowed tuples with a db.select, but this mean the select is performed only once.
I want to check the users every time the POST is called, so I just replaced the "allowed" variable with a db.select this way:
form.Validator('Username already exists.', lambda x: x not in [o.usr for o in db.select('users',what='usr')])
If I test "x not in [o.usr..etc..etc..]" on the interpreter, this work..
>>> [o.usr for o in db.select('users',what='usr')]
0.0 (1): SELECT usr FROM users
[u'hhh', u'Fede', u'Vinz', u'Patro', u'Codino', u'Codino']
>>> x = "Fede"
>>> x not in [o.usr for o in db.select('users',what='usr')]
0.0 (2): SELECT usr FROM users
False
But when I run the code and I make a new registration with an existing username nothing happens.. as you can see the "Codino" username is been registered twice.
What I'm doing wrong?
..and more interesting: there is a smarter way to block the registration of an already used username?
Thanks,
Federico
I don't know if you already have an answer with this one since it's an old thread.
Here's what I did on checking if username already exist.
I create a validator such as these:
vuser_exist = form.Validator("Username already exist.", lambda u: u is None or model.get_user(u.username) is None)
register_form = form.Form(
form.Textbox("username", vuser_req, vuser_length, description="Username"),
form.Button("submit", type="submit", description="Register"),
validators = [vuser_exist],
)
In model.py
def get_user(user):
try:
return db.select('users', where='user=$user', vars=locals())[0]
except IndexError:
return None
I have the following Django Model:
class State(models.Model):
name = models.CharField(max_length=80,null=False)
latitude = models.CharField(max_length=80,null=False)
longitude = models.CharField(max_length=80,null=False)
def __unicode__(self):
return self.name
In my views.py file I've created the following method:
def getCoords(request):
if request.is_ajax():
if request.method == 'POST':
try:
state = request.POST['state'] #receives the state from JS
stateInstance = State.objects.get(name=state)
stateLat = stateInstance.latitude
stateLong = stateInstance.longitude
data = {"lat" : stateLat, "long" : stateLong}
except State.DoesNotExist:
return HttpResponse("No record")
return HttpResponse(simplejson.dumps(data)) #returns json
So I'm sending this method a param through ajax, let's say "California". The state variable gets the value (I have proved that) but the query doesn't get executed, it returns the query doesn't match message. I've tried with the following as well:
state = request.POST['state']
if state == 'California':
return HttpResponse("Yes!")
else:
return HttpResponse(state)
When this snippet returns the state it displays California which means state's value is correct but the query is not executed properly. I don't know what's going on. Any thoughts?
Make sure you're connecting to the correct database in your settings.py. Make sure the record actually does exist with plain old SQL.
SELECT * FROM [app_name]_state WHERE name = 'California';
Also, try it from the Django shell. python manage.py shell
>>> from [app_name].models import State
>>> s = State.objects.get(name='California')
>>> s
<State: California>
Make sure that what is actually being sent is sent as POST, and not accidentally a GET variable in the URL. Also, make sure that the post variable has no extra characters and that it is actually valid. A decent way to do that is to just print it to the console, or if AJAX, with Firebug.
# Single quotes added so you can see any extra spaces in the console
print "'%s'" % state
Excuse the vague title, I didn't know how else to state this.
I have a task worker request handler that fetches data from a URL and writes it to blobstore and saves the data's blob_key to a ListProperty in datastore. I've tried to simplifly the code for clarity here:
class Fetch(webapp2.RequestHandler):
def get(self):
url = self.request.get('url')
itemKey = self.request.get('itemKey')
item = MyModel.get(itemKey)
try:
result = urlfetch.fetch(url=url)
if result.status_code == 200:
saveDataResult = save_data(result.content, itemKey)
if saveDataResult is False:
raise Exception('error saving data')
else:
raise Exception('error fetching data: %s' % result.status_code)
item.status = 'success'
except Exception:
item.status = 'failed'
finally:
item.put()
def save_data(data, itemKey)
try:
#write data to blobstore and get its blob_key...
blob_key = files.blobstore.get_blob_key(file_name)
item = MyModel.get(itemKey)
item.blobKey.append(blob_key)
item.put()
return True
except:
return False
Now the problem I'm having is, when saveDataResult returns True, its status is set to 'success' but its blobKey property contains no value, even though a blob_key was generated and the data successfully written. I can't see what's causing this to save my life, please help.
Without much more information it is very difficult to determine what's happening. Here's my educated guess:
MyModel.get(itemKey) is called both in get() and save_data(). I surmise that it's returning two different objects representing the item. When the blobKey gets updated in save_data, the update is occurring only in the object fetched in save_data. When you later examine it outside that scope, you're looking at a different object.
Whether this is correct or not will depend on the implementation of MyModel.get().
Also, you do realize that you're calling item.put() twice, right?
The problem is here
finally:
item.put()
this single call overrides the data saved by save_data() because it references an older object of item.
My suggestion would be you do the status updates from save_data() i.e item.status = 'success'
or move item = MyModel.get(itemKey) to come after save_data() so you can fetch the updated object.
The problem is that when you call save_data() with item = MyModel.get(itemKey)
which is again called from the class Fetch you end up having two different objects and therefore overwriting the one in save_data() and hence when you go to your model datastore no data for blobkey is stored as its overwritten.
Try doing everything in the class or you do not use item = MyModel.get(itemKey) twice.